栈
特点:先进后出、后进先出
1、顺序栈
依赖数组实现
代码实现(笔试极其重要,容易让你手写)
**#include<iostream>
using namespace std;
//顺序栈 C++容器适配器 stack (入栈push 出栈pop 获取栈顶元素top 判断栈空empty 获取栈的元素size)
class SeqStack
{
public:
SeqStack(int size = 10)
:mtop(0)
, mcap(size)
{
mpStack = new int[mcap];
}
~SeqStack()
{
delete[]mpStack;
mpStack = nullptr;
}
public:
//入栈
void push(int val)
{
if (mtop == mcap)//满了,需要进行栈扩容
{
//栈扩容
expand(2*mcap);
}
mpStack[mtop++] = val;
}
//出栈
void pop()
{
if (mtop == 0)
throw "stack is empty";//抛异常
mtop--;
return;
}
//获取栈顶元素
int top() const
{
if (mtop == 0)
throw "stack is empty";//抛异常
return mpStack[mtop-1];
}
//判断是否栈空
bool empty()const
{
return mtop == 0;
}
//判断栈元素个数
int size()const { return mtop; }
private:
void expand(int size)
{
int* p = new int[size];
memcpy(p,mpStack,mtop*sizeof(int ));
delete[]mpStack;
mpStack = p;
mcap = size;
}
private:
int* mpStack;
int mtop; //栈顶位置
int mcap; //栈空间大小
};**
测试代码
int main()
{
int arr[] = {5,10,66,77,88,99,100,111};
SeqStack s;
for (int v : arr)
{
s.push(v);
}
while (!s.empty())
{
cout << s.top() << " ";
s.pop();
}
cout << endl;
}
运行结果
2、链式栈
依赖链表实现
代码实现
//顺序栈 C++容器适配器 stack (入栈push 出栈pop 获取栈顶元素top 判断栈空empty 获取栈的元素size)
class LinkStack
{
public:
LinkStack():size_(0)
{
head_ = new Node;
}
~LinkStack()
{
Node* p = head_;
while (p != nullptr)
{
head_ = head_->next_;
delete p;
p = head_ ;
}
}
public:
//入栈 吧链表头节点后面,第一个有效节点的位置,当作栈顶位置
void push(int val)
{
Node* node = new Node(val);
node->next_ = head_->next_;
head_->next_ = node;
size_++;
}
//出栈
void pop()
{
if (head_->next_ == nullptr)
throw "stack is empty";
Node* p = head_->next_;
head_->next_ = p->next_;
delete p;
size_--;
}
//获取栈顶元素
int top() const
{
if (head_->next_ == nullptr)
throw "stack is empty";
return head_->next_->data_;
}
//判空
bool empty()
{
return head_->next_ == nullptr;
}
//返回栈元素个数 如果遍历一遍链表,记录节点个数为O(n) 想达到O(1)
int size()const
{
return size_;
}
private:
struct Node
{
Node(int data = 0) :data_(data), next_(nullptr) {}
int data_;
Node* next_;
};
Node* head_;
int size_;
};
测试代码
int main()
{
int arr[] = { 5,10,66,77,88,99,100,111 };
LinkStack s;
for (int v : arr)
{
s.push(v);
}
while (!s.empty())
{
cout << s.top() << " ";
s.pop();
}
cout << endl;
}
运行结果
3、笔试面试常见问题
1)括号匹配问题
思路:
1、便利s串,遇到左括号直接入栈
2、如果遇到右括号,从栈顶取出一个左括号
如果匹配,继续遍历s串中下一个括号
如果不匹配,直接结束掉
代码实现
class Solution {
public:
bool isValid(string s) {
stack<char> cs;
for(char ch:s)
{
if(ch == '('||ch=='['||ch=='{')
{
cs.push(ch);
}
else
{
//防止只有右括号
if(cs.empty())
return false;
//遇到右括号了
char cmp = cs.top();
cs.pop();
if(ch==')' && cmp != '('
|| ch == ']' && cmp !='['
|| ch == '}' && cmp !='{')
{
return false;
}
}
}
//栈里面的括号没处理完,防止左括号多
//return true;//err
return cs.empty();
}
};
2)逆波兰表达式求解
逆波兰表达式
是一种后缀表达式,所谓后缀就是指算符写在后面。
平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。
该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。
逆波兰表达式主要有以下两个优点:
去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中
思路:
用栈求解四则运算表达式:2+(4+6)/2+6/3
1、中缀表达式 =>逆波兰表达式(后缀表达式)
2、栈计算逆波兰表达式
1、遇到数字直接入栈
2、遇到符号,出栈两个数字
3、运算两个数字,吧结果再入栈
代码实现
class Solution {
public:
int calc(int left,int right,char sign)
{
switch(sign)
{
case '+':
return left + right;
case '-':
return left - right;
case '*':
return left * right;
case '/':
return left / right;
}
throw"";
}
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(left,right,str[0]));
}
else
{
//遇到数字,直接入数字栈
//string => int 使用 stoi stol
//int => string 使用 to_string()
intStack.push(stoi(str));
}
}
return intStack.top();
}
};
3)中缀转后缀表达式
理论讲解
遇到数字,直接输出,
遇到符号:
1、栈为空,符号直接入栈
2、如果是‘(’,直接入栈
3、用当前符号和栈顶符号比较其优先级
当前符号优先级 > 栈顶符号 – 当前符号直接入栈,结束
当前符号优先级 < 栈顶符号 – 栈顶符号出栈并输出,继续比较
1、吧栈里面符号都出完了
2、遇到 ‘)’,要一直出栈,直到遇见 ‘(’ 为止
代码实现
#include<iostream>
#include<string>
#include<stack>
using namespace std;
//比较符号有限级
bool Priority(char ch, char topch)
{
if ((ch == '*' || ch == '/') && (topch == '+' || topch == '-'))
return true;
if (ch == ')')
return false;
if (topch == '(' && ch != ')')
return true;
//if (ch == ')')
// return false;
return false;
}
//中缀表达式 => 后缀表达式
//这里默认都为正整数
string MiddleToEndExpr(string expr)
{
string result;
stack<char> s;
for (char ch : expr)
{
if (ch >= '0' && ch <= '9')
{
result.push_back(ch);
}
else
{
for(;;)
{
//处理符号
if (s.empty() || ch == '(')
{
s.push(ch);
break;
}
//比较当前符号和栈顶符号的优先级
char topch = s.top();
//计算优先级
//Priority:true ch > topch fales ch <= topch
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;
}
代码测试
int main()
{
cout << MiddleToEndExpr("(1+2)*(3+4)") << endl;
cout << MiddleToEndExpr("2+(4+6)/2+6/3") << endl;
cout << MiddleToEndExpr("2+6/(4-2)+(4+6)/2") << endl;
cout << MiddleToEndExpr("") << endl;
}