C++数据结构--栈
一.什么是栈
栈(Stack)是一种遵循“后进先出”(LIFO, Last In First Out)原则的线性数据结构。栈通常有两种实现方式:顺序栈与链式栈,两者各有优劣。但同时从底层来看栈并不是一种新的数据结构,顺序栈的底层依靠数组,链式栈的底层依靠链表。(注意C++中的容器适配器stack,底层默认是deque(双向队列)是一个顺序栈,但是我们也可以将其设置成一个链式栈)
二.顺序栈及其代码实现
原理:基于数组实现,使用一段连续的内存空间来存储元素。
优点:实现简单,访问速度快(内存连续,对CPU缓存友好)。
缺点:需要预先分配固定大小的空间,或者在空间不足时进行扩容,扩容操作有一定开销。
代码实现如图(这里我们借助vector实现,也可以借助array实现,不过需要手动扩容,更繁琐):
注意这里最好在使用pop_back或back前判断栈是否为空,否则会造成未定义行为。
class stack
{
private:
vector<int>mpstack;
public:
stack()
{ }
public:
//入栈操作
void push(int val)
{
mpstack.push_back(val);
}
//出栈操作
void pop()
{
if (mpstack.empty())
{
throw "mpstack is empty";
}
mpstack.pop_back();
}
//获取栈顶操作
int top()
{
if (mpstack.empty())
{
throw "mpstack is empty";
}
return mpstack.back();
}
//判断栈是否为空
bool empty()
{
return mpstack.empty();
}
};
三.链式栈及其代码实现
原理:基于链表实现,每个元素(节点)包含数据和指向下一个节点的指针。
优点:动态分配内存,没有固定的容量限制,无需扩容。
缺点:每个节点需要额外的空间存储指针,内存不连续,访问效率略低于数组。
代码实现如图(这里借助了list实现,更为简单,只需将上面的代码中vector改成list即可):
class stack
{
private:
list<int>mpstack;
public:
stack()
{ }
public:
//入栈操作
void push(int val)
{
mpstack.push_back(val);
}
//出栈操作
void pop()
{
if (mpstack.empty())
{
throw "mpstack is empty";
}
mpstack.pop_back();
}
//获取栈顶操作
int top()
{
if (mpstack.empty())
{
throw "mpstack is empty";
}
return mpstack.back();
}
//判断栈是否为空
bool empty()
{
return mpstack.empty();
}
};
四.栈的应用场景
栈无论是在学习中,还是在工程中都极其重要,有十分广的应用场景。例如:
函数调用(即函数栈帧,详见:函数栈帧的创建与销毁):程序在执行函数时,会使用一个“调用栈”来保存函数的局部变量、参数和返回地址等信息。函数调用结束时,对应的信息会从栈中弹出。
括号匹配问题(leetcode-20题):编译器或解释器使用栈来检查代码中的括号(如 (), [], {})是否正确配对和嵌套。遇到左括号时压入栈,遇到右括号时从栈中弹出一个左括号进行匹配。
撤销/重做功能:许多软件(如文本编辑器)使用栈来记录用户的操作历史,从而实现撤销(Undo)和重做(Redo)功能。
浏览器前进/后退:当你浏览网页时,访问过的页面URL会被压入一个栈中,点击“后退”按钮时,就从栈中弹出上一个页面的URL。
表达式求值:在计算数学表达式(尤其是将中缀表达式转换为后缀表达式)时,栈是核心工具。
深度优先搜索 (DFS):在图或树的遍历算法(即非递归实现深度优先搜索,依靠栈实现)中,栈被用来记录访问路径,是实现深度优先搜索的关键。
五.栈的经典问题
1.有效的括号(leetcode20)
核心思路:定义一个栈,遇到左括号就一直入栈,遇到右括号就出栈两个,判断出栈的两个是否匹配。
class Solution {
public:
bool isValid(string s) {
stack<char>stk;
if(s.length()==1||s[0]==')'||s[0]==']'||s[0]=='}')
{
return false;
}
for(auto ch:s)
{
if(ch=='('||ch=='['||ch=='{')
{
stk.push(ch);
}
else if(!stk.empty())
{
if(stk.top()=='('&&ch==')'||stk.top()=='['&&ch==']'||stk.top()=='{'&&ch=='}')
{
stk.pop();
}
else
{
return false;
}
}
else
{
return false;
}
}
if(stk.empty())
{
return true;
}
else
{
return false;
}
}
};
2.逆波兰表达式求值(leetcode150)
思路与括号匹配问题很像:定义一个栈,遍历是数字就入栈,是符号就出栈两个,将出栈的两个进行该符号的运算,并将结果再入栈。
class Solution {
public:
int calc(int right,int left,char sigh)
{
switch(sigh)
{
case '+':
return left+right;
case '-':
return left-right;
case '*':
return left*right;
case '/':
return left/right;
}
return 0;
}
int evalRPN(vector<string>& tokens) {
stack<int> intstack;
for(string &str:tokens)
{
if(str.size()==1
&&(str[0]=='+'
||str[0]=='-'
||str[0]=='*'
||str[0]=='/'))
{
int right=intstack.top();
intstack.pop();
int left=intstack.top();
intstack.pop();
intstack.push(calc(right,left,str[0]));
}
else
{
intstack.push(stoi(str));
}
}
return intstack.top();
}
};
3.中缀转后缀表达式
将普通的表达式转变为逆波兰表达式:核心思路:定义一个栈用于存放字符,定义数字,直接输出,如果栈为空或遍历到(,直接入栈,其他的符号入栈时进行优先级比较,认为所有的符号优先级都比(大,遇到),输出栈中的元素直到(出栈,最后剩余的元素直接全部出栈。
bool Priority(char ch, char topch)
{
if ((ch == '*' || ch == '/') && (topch == '+' || topch == '-'))
return true;
if (topch == '(' && ch != ')')
return true;
return false;
}
string MiddleToEndExpr(string exper)
{
string result;
stack<char> s;
for (char ch : exper)
{
if (ch >= '0' && ch <= '9')
{
result.push_back(ch);
}
else
{
while (1)
{
if (s.empty() || ch == '(')
{
s.push(ch);
break;
}
char topch = s.top();
if (Priority(ch, topch))
{
s.push(ch);
break;
}
else
{
s.pop();
if (topch == '(')
break;
result.push_back(topch);
}
}
}
}
while (!s.empty())
{
result.push_back(s.top());
s.pop();
}
return result;
}
更多推荐


所有评论(0)