news 2026/11/6 5:45:20

数据结构与算法实战:栈队列实现与应用解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
数据结构与算法实战:栈队列实现与应用解析

1. 数据结构与算法实战:从基础栈队列到经典问题解析

在算法竞赛和编程面试中,栈、队列以及它们的变体是最基础也最常用的数据结构。很多复杂问题都可以通过巧妙运用这些基础结构来解决。本文将带大家从零开始实现基础的栈和队列,逐步深入到括号匹配、后缀表达式计算等经典问题,最后攻克NOIP级别的算法题。

2. 基础数据结构实现

2.1 数组模拟栈的实现

栈是一种LIFO(后进先出)的数据结构,就像一叠盘子,你只能从最上面拿取或放入。用数组模拟栈的关键是维护一个栈顶指针:

const int maxn = 1e6+100; ull a[maxn], cnt; // 栈数组和栈顶指针 void push(ull x) { a[++cnt] = x; // 栈顶指针先+1再存值 } void pop() { if(cnt > 0) cnt--; // 指针下移相当于删除 } ull top() { return a[cnt]; // 访问栈顶元素 }

注意:在实际工程中,应该检查栈是否为空再进行pop或top操作,这里简化为核心逻辑

2.2 数组模拟队列的实现

队列是FIFO(先进先出)结构,就像排队买票。数组模拟队列需要维护队首和队尾两个指针:

const int maxn = 2e6+100; ll a[maxn]; int L = 1e6, R = 1e6-1; // 初始时队尾在队首左侧 void enqueue(ll x) { a[++R] = x; // 队尾右移插入 } void dequeue() { if(L <= R) L++; // 队首右移删除 } ll front() { return a[L]; // 访问队首 }

技巧:将初始指针设置在数组中间可以避免过早的越界问题

3. 栈的经典应用场景

3.1 括号匹配问题

括号匹配是栈的经典应用,算法思路是:

  1. 遇到左括号入栈
  2. 遇到右括号时检查栈顶是否匹配
  3. 最后检查栈是否为空
bool isValid(string s) { stack<char> st; for(char c : s) { if(c == '(') st.push(c); else if(c == ')') { if(st.empty()) return false; st.pop(); } } return st.empty(); }

时间复杂度:O(n),空间复杂度:O(n)

3.2 后缀表达式计算

后缀表达式(逆波兰表示法)不需要括号就能明确运算顺序。计算步骤:

  1. 遇到数字入栈
  2. 遇到运算符时弹出栈顶两个数运算
  3. 将结果压回栈中
int evalRPN(vector<string>& tokens) { stack<int> st; for(string s : tokens) { if(s == "+" || s == "-" || s == "*" || s == "/") { int b = st.top(); st.pop(); int a = st.top(); st.pop(); if(s == "+") st.push(a + b); else if(s == "-") st.push(a - b); else if(s == "*") st.push(a * b); else st.push(a / b); } else { st.push(stoi(s)); } } return st.top(); }

注意除法和减法的操作数顺序,a和b不能颠倒

4. 队列的高级应用

4.1 滑动窗口最值问题

单调队列可以在O(n)时间内解决滑动窗口最值问题。以最大值为例:

vector<int> maxSlidingWindow(vector<int>& nums, int k) { deque<int> dq; vector<int> res; for(int i = 0; i < nums.size(); ++i) { // 移除超出窗口的元素 if(!dq.empty() && dq.front() == i - k) dq.pop_front(); // 维护单调递减队列 while(!dq.empty() && nums[dq.back()] < nums[i]) dq.pop_back(); dq.push_back(i); // 窗口形成后记录结果 if(i >= k - 1) res.push_back(nums[dq.front()]); } return res; }

4.2 单调队列优化DP

单调队列可以优化某些DP问题,如带限制的最大子段和:

int constrainedMaxSum(vector<int>& nums, int k) { deque<int> dq; vector<int> dp(nums.size()); int res = nums[0]; dp[0] = nums[0]; dq.push_back(0); for(int i = 1; i < nums.size(); ++i) { // 移除超出范围的索引 while(!dq.empty() && dq.front() < i - k) dq.pop_front(); dp[i] = max(nums[i], dp[dq.front()] + nums[i]); res = max(res, dp[i]); // 维护单调递减队列 while(!dq.empty() && dp[dq.back()] <= dp[i]) dq.pop_back(); dq.push_back(i); } return res; }

5. 单调栈的妙用

5.1 下一个更大元素

单调栈可以高效解决"下一个更大元素"类问题:

vector<int> nextGreaterElements(vector<int>& nums) { stack<int> st; vector<int> res(nums.size(), -1); for(int i = 0; i < nums.size(); ++i) { while(!st.empty() && nums[st.top()] < nums[i]) { res[st.top()] = nums[i]; st.pop(); } st.push(i); } return res; }

5.2 柱状图中最大矩形

单调栈解决柱状图最大矩形问题的模板:

int largestRectangleArea(vector<int>& heights) { stack<int> st; heights.push_back(0); // 哨兵 int res = 0; for(int i = 0; i < heights.size(); ++i) { while(!st.empty() && heights[st.top()] > heights[i]) { int h = heights[st.top()]; st.pop(); int w = st.empty() ? i : i - st.top() - 1; res = max(res, h * w); } st.push(i); } return res; }

6. 综合应用:NOIP2011选择客栈

这个问题需要统计满足特定条件的客栈组合数。优化思路是利用前缀和:

int solve(vector<int>& color, vector<int>& cost, int p) { int n = color.size(); vector<int> last(n + 1, -1); // 记录每种颜色上次满足条件的位置 vector<int> cnt(n + 1, 0); // 记录每种颜色的累计满足条件的客栈数 int res = 0, valid_pos = -1; for(int i = 0; i < n; ++i) { if(cost[i] <= p) { valid_pos = i; // 更新最近满足条件的位置 } if(valid_pos >= last[color[i]]) { cnt[color[i]] = 0; // 重置计数 last[color[i]] = i; } res += cnt[color[i]]; cnt[color[i]]++; } return res; }

这个解法的时间复杂度是O(n),空间复杂度是O(n),能够高效处理大规模数据。

7. 性能优化与调试技巧

在实际编码中,有几个常见陷阱需要注意:

  1. 边界条件处理:特别是栈/队列为空时的操作
  2. 指针初始化:队列的队首队尾指针初始位置要合理
  3. 循环不变式:维护单调性时要确保不破坏性质
  4. 输入规模:提前预留足够的数组空间

调试时可以:

  • 打印栈/队列的中间状态
  • 对小的测试用例手动模拟
  • 使用assert检查不变式

对于栈溢出问题,可以:

  1. 检查递归深度是否过大
  2. 改用迭代实现
  3. 增加栈空间(某些编译器支持)

在算法竞赛中,我通常会预先编写好经过测试的栈和队列模板,包含常用操作和边界检查,这样可以减少编码错误。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/6 5:45:18

西门子PLC恒压供水系统设计与PID控制实现

1. 项目背景与核心需求在工业自动化领域&#xff0c;恒压供水系统是保障生产生活用水稳定的关键基础设施。传统供水系统存在压力波动大、能耗高、响应慢等问题&#xff0c;而采用西门子PLC控制的恒压供水系统能够实现精确的压力调节和智能化运行。这个项目的核心目标是开发一套…

作者头像 李华
网站建设 2026/11/6 5:44:39

TTAO-BP-KDE混合算法在区间预测中的应用与优化

1. 项目背景与核心价值在工业预测和金融分析领域&#xff0c;传统的点预测方法往往难以应对复杂系统中的不确定性。这个项目实现了一种融合三角拓扑聚合优化&#xff08;TTAO&#xff09;、BP神经网络和核密度估计&#xff08;KDE&#xff09;的混合算法&#xff0c;专门解决多…

作者头像 李华
网站建设 2026/11/6 5:43:11

010 Editor二进制解析:模板与脚本定位配置块

简介&#xff1a;这是一套面向开发人员、系统管理员及逆向与数据分析学习者的010 Editor十六进制编辑工具包&#xff0c;重点解决二进制文件查看修改、大文本检索替换和复杂文件结构解析等需求。压缩包共35个文件&#xff0c;约19.64MB&#xff0c;以dll运行库、exe主程序、qhc…

作者头像 李华
网站建设 2026/11/6 5:42:55

分布式锁原理与实现方案全解析

1. 分布式锁核心概念解析在分布式系统中&#xff0c;多个服务实例对共享资源进行并发访问时&#xff0c;分布式锁是保证数据一致性的关键机制。与单机环境下的线程锁不同&#xff0c;分布式锁需要解决网络延迟、节点故障等特有挑战。典型的应用场景包括&#xff1a;秒杀系统中的…

作者头像 李华
网站建设 2026/11/6 5:42:55

数据结构与算法:提升开发效率的核心技术

1. 为什么数据结构与算法如此重要记得刚入行时&#xff0c;我接手过一个看似简单的用户推荐系统优化任务。原本运行良好的系统随着用户量增长突然变得异常缓慢&#xff0c;查询延迟从200ms飙升到5秒以上。经过三天三夜的排查&#xff0c;最终发现问题出在一个嵌套循环的匹配算法…

作者头像 李华
网站建设 2026/11/6 5:41:59

最小栈问题解析与O(1)时间复杂度实现

1. 最小栈问题解析155.最小栈是算法面试中的经典问题&#xff0c;要求设计一个栈数据结构&#xff0c;除了常规的push和pop操作外&#xff0c;还能在常数时间内检索到栈中的最小元素。这个问题看似简单&#xff0c;但考察了数据结构设计的核心思想——如何在不同的操作之间进行…

作者头像 李华