文章目录
Stack 栈
栈也是一种线性表,只不过我们对这个线性表进行了约束:只能从这个线性表的一端进行插入和删除操作,即LIFO(Last In First out)这一端称为栈顶,另一端称为栈底,不含任何元素则为空栈
ArrayStack 顺序栈
栈的顺序存储:
#define MAX_SIZE 100
class ArrayStack{
private:
ElementType data[MAX_SIZE];
int top; // initialized as -1
public:
//...
};
isEmpty
判断栈是否为空:
bool isEmpty(){
return this->top == -1;
}
push
进栈一个元素:
void push(ElementType e){
if(this->top >= MAX_SIZE){
std::cout << "Stack is FULL, failed to push!" << std::endl;
return;
}
this->top++;
this->data[this->top] = e;
return;
}
pop
出栈一个元素:
ElementType pop(){
if(isEmpty()){
return DEFAULT_VALUE;
}
this->top--;
return this->data[this->top + 1];
}
getTop
返回栈顶的元素:
ElementType getTop(){
if(isEmpty()){
return DEFAULT_VALUE;
}
return this->data[this->top];
}
clear
清空栈:
void clear(){
this->top = -1;
return;
}
Shared Stack 共享栈
即两个栈共享一份连续的存储空间,一个栈从后往前进栈,另一个栈从前往后面进栈:
![](https://i-blog.csdnimg.cn/blog_migrate/58a03d1f856579002bbbf574fc7ce735.jpeg)
LinkedStack 链式栈
栈的链式存储:
class LinkedStack{
private:
typedef struct Node{
ElementType e;
Node* next;
Node(ElementType e_):e(e_){}
}Node;
Node* top;
int size;
public:
// ...
};
采用链式存储的栈通常不需要担心堆栈堆满的情况
isEmpty
bool isEmpty(){
return this->top == NULL;
}
pop
ElementType pop(){
if(isEmpty()){
return DEFAULT_VALUE;
}
ElementType value = this->top->e;
this->top = top->next;
return value;
}
push
void push(ElementType e){
Node* newNode = new Node(e);
newNode->next = this->top;
this->top = newNode;
return;
}
getTop
ElementType getTop(){
if(isEmpty()){
return DEFAULT_VALUE;
}
return this->top->e;
}
clear
void clear(){
while(this->top != NULL){
Node* p = this->top;
this->top = this->top->next;
delete p;
}
return;
}
序列出栈问题
一个给定序列的出栈情况总数为:
(0)
1
n
+
1
C
2
n
n
=
1
n
+
1
(
2
n
)
!
n
!
×
n
!
\frac{1}{n+1}C^{n}_{2n}=\frac{1}{n+1}\frac{(2n)!}{n!\times n!}\tag{0}
n+11C2nn=n+11n!×n!(2n)!(0)
这个公式称为卡特兰(Catalan)数,可以用数学归纳法证明
关于表达式求值以及输出其他表示方法
比如,输入一个表达式(只含有简单的四则运算,包含括号)的字符串,要求我们得出这个表达式的值,我们可以利用栈这个数据结构来实现:
- 操作数栈:存储输入的操作数
- 运算符栈:存储输入的运算符
中缀求值
假如输入的是中缀表达式,即我们常见的表达式顺序(假定输入是合法的):
- 下一个是操作数,将操作数入栈
- 下一个是操作符,且这个操作符的优先级小于当前栈顶的操作符的优先级,将栈顶的操作符出栈,操作数栈中的两个操作数出栈,得到运算结果入栈,再将下一个操作符入栈
对于括号运算符: - 下一个是左括号,入栈
- 下一个右括号,将括号内所有运算符出栈直到遇到一个左括号,将这个左括号也出栈,得到运算结果入栈
可以通过Map
将运算符和其对应的优先等级存储起来,可以快速查找得到运算符相应的运算优先级:
std::map<char, int> opToPrior;
opToPrior.insert(std::make_pair('+', 1));
opToPrior.insert(std::make_pair('-', 1));
opToPrior.insert(std::make_pair('*', 2));
opToPrior.insert(std::make_pair('/', 2));
opToPrior.insert(std::make_pair('(', 3));
opToPrior.insert(std::make_pair(')', 3));
其中:
map.insert(std::pair<type1, type2> pair)
:在make_pair(type1, type2)
:返回一个pair<type1, type2>
表达式求值函数:
void calculate(string formula){
LinkedStack<int> oprands;
LinkedStack<char> ops;
int i = 0;
int result = 0;
int priorityOfTop = 100;
for(;i < formula.length();i++){
if(isDigit(formula[i]){
int oprand = 0;
while(formula[i] != ' '){
oprand *= 10;
oprand += formula[i] - 48;
i++;
}
oprands.push(oprand);
}
else{
int priority = opToPriority[formula[i]];
if(pirority < priorityOfTop){
// then temp is real
if(oprands.size() >= 0){
int b = oprands.pop();
int a = oprands.pop();
int temp = 0;
switch(ops.top()){
case '+':
temp = a + b;
break;
case '-':
temp = a - b;
break;
case '*':
temp = a * b;
break;
case '/':
temp = a / b;
break;
default:
break;
}
oprands.push(temp);
ops.pop();
}
}
else if(formula[i] == ')'){
// pop all op until there is a '(' at top of the stack.
// finish all the calculation and push result into
// oprand stack.
while(ops.top() != '('){
int b = oprands.pop();
int a = oprands.pop();
int temp = 0;
switch(ops.top()){
case '+':
temp = a + b;
break;
case '-':
temp = a - b;
break;
case '*':
temp = a * b;
break;
case '/':
temp = a / b;
break;
default:
break;
}
oprands.push(temp);
ops.pop();
}
ops.pop();
}
ops.push(formula[i]);
pirorityOfTop = formula[i];
// pass blank
i++;
}
}
return oprands.pop();
}
后缀求值
事实上,后缀求值是最简单的,因为后缀表达式已经完成了运算符优先级的整理,例如: a + b ∗ c a+b*c a+b∗c 其后缀表达式为: a b c ∗ + abc*+ abc∗+,即先进行乘法然后进行加法,所以只需遍历输入的后缀表达式,执行每次遇到的运算符对应的运算即可,中间都有空格隔开,以整型为例:
int calculate(string formula){
// using template, try it yourself
LinkedStack<int> oprands;
int i = 0;
for(;i < formula.length();i++){
if(isDigit(formula[i])){
int oprand = 0;
while(formula[i] != ' '){
oprand *= 10;
oprand += formula[i] - 48;
i++;
}
oprands.push(oprand);
}
else{
switch(formula[i]){
// attention!
// right oprand is on the top!
int b = oprands.pop();
int a = oprands.pop();
case '+':
oprands.push(a + b);
break;
case '-':
oprands.push(a - b);
break;
case '*':
oprands.push(a * b);
break;
case '/':
oprands.push(a / b);
break;
default:
break;
}
// pass blank;
i++;
}
}
// using pop so that we don't need to call clear()
return oprands.pop();
}
前缀求值
同理,前缀求值也是一样,我们不需要对优先级进行处理,只需要从右往左扫描,采用后缀表达式相同的计算方法即可:
int calculate(string formula){
// using template, try it yourself
LinkedStack<int> oprands;
int i = formula.length() - 1;
// get oprands
for(;i > 0;i--){
if(isDigit()){
int oprand = 0;
int j = i;
while(formula[i] != ' '){
int num = formula[i] - 48;
for(int k = 0;k < j - i;k++){
num *= 10;
}
oprand += num;
i--;
}
oprands.push(oprand);
}
else{
// attention!
// left oprand is on the top, which is different with that above
int a = oprands.pop();
int b = oprands.pop();
switch(formula[i]){
case '+':
oprands.push(a + b);
break;
case '-':
oprands.push(a - b);
break;
case '*':
oprands.push(a * b);
break;
case '/':
oprands.push(a / b);
break;
default:
break;
}
// pass blank;
i--;
}
}
// using pop so that we don't need to call clear()
return oprands.pop();
}
中缀转后缀
实际上中缀转后缀就是将计算中缀表达式中用一个操作数栈来存储操作数的方式改为遇到一个操作数直接输出的方法(如果是转为一个后缀表达式的字符串的话,就将读到的数字字符直接连接到输出字符串后面,读完一个完整的数之后再间隔上一个空格即可),然后执行运算改为输出运算符,这样就实现了中缀到后缀的转换了。
中缀转前缀
中缀转前缀则需要的是一个操作数栈,遇到操作符优先输出,然后这个操作符之前的操作数也可以直接输出,然后同样,如果是可以执行的计算的话,那么操作符后面的操作数也可以输出,这样就实现了中缀到前缀的转换了。
括号匹配问题
输入长度为
n
n
n 的括号(包含'{', '}', '[', ']', '(', ')'
)字符串,判断格式是否正确,即符合括号的任意嵌套组合顺序,而例如"[{]}"
这样的格式则为不符合要求的字符串。
在每次读进一个新的字符时:
- 左括号:进栈
- 右括号:判断是否和栈顶的字符是相互匹配的——即成对的括号,并且是先左后右的,如果匹配则将这对括号出栈,不匹配则字符串不符合要求
重复以上结果,直到读完全部字符,如果此时栈为空,则符合要求,否则不符合
bool parenthesesMatch(string str){
// This situation's result depends on the requirement, generally true
if(str.length() == 0){
return true;
}
// initially push one character into stack so that we don't have to
// care about the situation where stack is empty, but before that
// we should make sure the first character is not a poor single dog
if(str[0] == ')' || str[0] == ']' || str[0] == '}'){
return false;
}
LinkedStack<char> stack;
stack.push(str[0]);
for(int i = 1;i < str.length();i++){
switch(str[i]){
// if right part, check if matched
case ')':
if(stack.top() == '('){
stack.pop();
}
else{
return false;
}
break;
case ']':
if(stack.top() == '['){
stack.top();
}
else{
return false;
}
break;
case '}':
if(stack.top() == '{'){
stack.pop();
}
else{
return false;
}
break;
// push right part into stack
default:
stack.push(str[i]);
break;
}
}
// check if empty
return stack.isEmpty();
}
迷宫问题
经典的迷宫问题可以通过栈来解决,这样我们可以在每次做出决策的时候保留决策位置,当进入死路的时候,我们可以从栈顶逐步返回最近的路口往新的方向前进。例如给出:
- 一个二维数组存储的地图
map
- 起点
(startX, startY)
- 地图的格子有三种可能:
' '
表示空地,'#'
表示墙壁,'*'
表示终点
二维的数组每次有四个方向可以前进:
- 用一个
outDegree
给出当前出度——初始化为4,表示可以选择下一步的个数,以及对应的下一个访问的方向(4,往上;3,往下;2,往左;1,往右),每选择一个方向(不管能不能走)将出度减一表示我们已经访问过这个方向了,下次回到这个结点的时候不必考虑这个方向 - 再用一个
inDegree
表示进入这个结点的方向,如果当前出度等于入度,则出度直接减一,跳过这个方向
typedef struct Grid{
char contain;
int xCoord;
int yCoord;
int outDegree = 4;
int inDegree;
Grid(char contain_, int x_coord, int y_coord, int in_)
:contain(contain_), xCoord(x_coord), yCoord(y_coord), inDegree(in_){}
}Grid;
void solution(char** map, int length, int width, int startX, int startY){
LinkedStack<Grid> route;
// initialize
route.push(Grid(map[startY][startX], startX, startY));
while(route.size != 0){
Grid g = route.top();
if(g.cotain == '*'){
std::cout << "Exit -> X: " << g.xCoord << " Y: " << g.yCoord << std::endl;
return;
}
if(g.inDegree == g.outDegree){
g.outDegree--;
continue;
}
switch(g.outDegree){
// move up if possible
case 4:
if(g.yCoord > 0 && map[g.yCoord - 1][g.xCoord] != '#'){
route.push(Grid(map[g.yCoord - 1][g.xCoord], g.xCoord, g.yCoord - 1, 2));
}
g.outDegree--;
break;
// move down if possible
case 3:
if(g.yCoord < length - 1 && map[g.yCoord - 1][g.xCoord] != '#'){
route.push(Grid(map[g.yCoord + 1][g.xCoord], g.xCoord, g.yCoord + 1, 4));
}
g.outDegree--;
break;
// move left if possible
case 2:
if(g.xCoord > 0 && map[g.yCoord - 1][g.xCoord] != '#'){
route.push(Grid(map[g.yCoord][g.xCoord - 1], g.xCoord - 1, g.yCoord - 1, 1));
}
g.outDegree--;
break;
// move right if possible
case 1:
if(g.xCoord < width - 1 && map[g.yCoord - 1][g.xCoord] != '#'){
route.push(Grid(map[g.yCoord][g.xCoord - 1], g.xCoord, g.yCoord - 1, 2));
}
g.outDegree--;
break;
// can't find a new route anymore, go back
case 0:
route.pop();
break;
default:
break;
}
}
// stack is empty now, that means:
std::cout << "No solution!" << std::endl;
return;
}
这是解决问题的一种方法——深度优先搜索(DFS,Depth First Search),我们通过栈来实现。那你很快就会想到——宽度优先搜索(BFS,Broad First Search),我们也可以利用这种方法,利用队列来解决这个问题。