1 算法思想
1.1含义
只允许在一端进行插入或删除的线性表。
栈顶:线性表允许进行插入和删除的一端
栈的顺序存储:
利用地址连续的存储单元存放栈的所有元素,同时用指针top指示当前栈顶的位置
类型描述:
#define Maxsize 50
Typedef struct{
ElemType data[MaxSize];
Int top;
}SqStack;
栈的链式存储
通常采用单链表实现,
类型描述:
typedef struct LinkNode{
ElemType data;
struct LinkNode *next;
} *LiStack;
1.2特点
后进先出
1.3适用
适用后进先出的题目,递归等
1.4通用解法
栈算法: 1确保符合后进先出的性质。 2实现特殊需求的栈时,可以考虑采用辅助栈。 |
1.5经典例题讲解
请设计一个栈,除pop与push方法,还支持min方法,可返回栈元素中的最小值。
push,pop,min三个方法的时间复杂度为O(1)
分析:
不要把思维局限在只有一个栈上,求最小值需要遍历该栈。而正确的做法直接用另外一个栈保存最小值,
里面记录了最小值,就不需要遍历操作。直接用另一个栈来记录最小值。压入时,除了栈本身压入元素外,
如果当前元素<=最小值,那么压入第二个栈中。【防止后面当前元素=最小值时弹出该元素后,后续就没有最小值】
弹出时,如果当前元素等于最小值时,第二个栈也弹出元素。
求最小值时,就返回当前栈的栈顶元素。
代码如下:
class MinStack : public stack<int> { public: MinStack(){} void push(int value) { stack<int>::push(value); //如果当前元素的值 <= 最小值,就压入最小元素,这里等于时要压入,否则后续当前元素=最小值 弹出元素后会导致下次遇到同样情况,无法弹出最小元素 if( value <= min() ) { minStack.push(value); } } void pop() { int value = stack<int>::top(); stack<int>::pop(); //如果当前值就是最小值,弹出最小栈中对应最小值 if(value == min()) { minStack.pop(); } } int min() { if( minStack.empty() ) { return INT_MAX; } else { return minStack.top(); } } private: stack<int> minStack; }; |
2 栈系列
类别-编号 |
题目 |
遁去的一 |
1 |
括号匹配问题 在某个字符串(长度不差过100)中有左括号、有括号和大小写字母;规定任何一个左括号都从内到外与在它右边且距离最近的右括号匹配。写一个程序,找到无法匹配的左括号和右括号,输出原来字符串,并在下一行标出不能匹配的括号。不能匹配的左括号用”$”标注,不能匹配的右括号用”?”标注
输入:多组数据,每组一行,一个字符串,只有左右括号和大小写字母,字串长度不超过100 输出:输入胡两行,第一行:源字符串,第二行:有$表示不匹配的左括号,?表示右括号,之间用空格 思路:使用栈,凡是左括号均进栈,凡是遇到右括号,判断栈顶是否是左括号,不是则说明发生错误,记录错误。 输入:)(rttyy())sss)( 输出: )(rttyy())sss)( ? ?$ |
计算机考研—机试指南 https://blog.csdn.net/qingyuanluofeng/article/details/47160101 关键: 1输出不是立即打印出来,而是先缓存在输出的字符数组中 2栈中存放的不应该是'(',而是存放的是字符数组的下标 3由于输入的是字符串,用其大小做限制循环 4给定条件是字符串长度是100,你声明的字符数组长度应该110,比它大
代码: int main(int argc,char* argv[]) { // char sInput[101]; char sOutput[101]; while(EOF!=scanf("%s",sInput)) { int i = 0; stack<int> stackInt; while('\0'!=sInput[i]) { if('('==sInput[i]) { stackInt.push(i); sOutput[i] = ' '; } //else if(')'==sOutput[i]) else if(')'==sInput[i]) { //如果栈不空,说明,可以找到与右括号匹配的左括号 if(!stackInt.empty()) { stackInt.pop(); //添加空格' ' sOutput[i] = ' '; } //栈空,说明该右括号不能被匹配,则修改相应位置输出为'?' else { sOutput[i] = '?'; } } //普通字符输出空格 else { sOutput[i] = ' '; } i++; }//while //如果最后栈不空,则替换相应位置输出为'$' while(!stackInt.empty()) { sOutput[stackInt.top()] = '$'; stackInt.pop(); } //打印原始字符串 printf("%s\n",sInput); //打印匹配字符串,要手动添加结束符 sOutput[i] = '\0'; printf("%s\n",sOutput); //printf("%s\n%s\n",sInput,sOutput); } system("pause"); getchar(); return 0; } |
2 |
简单计算器 读入只含 +,-,*,/的非负整数计算式,计算表达式的值
输入: 若干测试用例,每个测试用例占1行,每行<=200个字符,整数和字符之间用一个空格分隔。无非法表达式。遇只含有0的一行时,结束输出 输出: 输出1行该表达式的值,保留2位小数 输入: (1 + 2) 1 2 + (4 + 2 * 5 - 7 / 11) 4 2 5 * + 7 11 / - 0 输出: 3.00 13.36 |
计算机考研—机试指南 https://blog.csdn.net/qingyuanluofeng/article/details/47160151 思路:用栈,后缀表达式,首先*和/的优先级较高,碰到需要优先处理,其实就是中缀表达式转换为后缀表达式,然后每次栈顶为运算符时, 取栈顶元素A,和次栈顶元素B,计算 B 运算符 A 即可,得到的计算结果再存入栈 问题转化为:中缀转后缀 好像是从右向左扫描 7 / 11 ->
关键: 1 后缀表达式求值 后缀表达式也叫逆波兰表达式,其求值过程可以用到栈来辅助存储。假定待求值的后缀表达式为:6 5 2 3 + 8 * + 3 + *,则其求值过程如下: 1)遍历表达式,遇到的数字首先放入栈中,此时栈如下所示: 2)接着x到“+”,则弹出3和2,执行3+2,计算结果等于5,并将5压入到栈中。 3)读到8,将其直接放入栈中。 4)读到“*”,弹出8和5,执行8*5,并将结果40压入栈中。而后过程类似,读到“+”,将40和5弹出,将40+5的结果45压入栈...以此类推。最后求的值288。
2中缀表达式转后缀表达式 中缀表达式a + b*c + (d * e + f) * g,其转换成后缀表达式则为a b c * + d e * f + g * +。 转换过程需要用到栈,具体过程如下: 1)如果遇到操作数,我们就直接将其输出。 2)如果遇到操作符,则我们将其放入到栈中,遇到左括号时我们也将其放入栈中。 3)如果遇到一个右括号,则将栈元素弹出,将弹出的操作符输出直到遇到左括号为止。注意,左括号只弹出并不输出。 4)如果遇到任何其他的操作符,如(“+”, “*”,“(”)等,从栈中弹出元素直到遇到发现更低优先级的元素(或者栈为空)为止。弹出完这些元素后, 才将遇到的操作符压入到栈中。有一点需要注意,只有在遇到" ) "的情况下我们才弹出" ( ",其他情况我们都不会弹出" ( "。 5)如果我们读到了输入的末尾,则将栈中所有元素依次弹出。
参考: https://www.cnblogs.com/hantalk/p/8734511.html
1设定数字栈和运算符栈, 遇到数字直接压入数字栈中 遇到运算符,{若当前运算符#比栈顶运算符优先级小 或者 栈为空,则压入当前运算符 {否则,从数字栈中弹出栈顶元素A,次栈顶元素B,计算 B # A,将计算后的结果压入数字栈中 需要为该表达式设定运算符(和),两者的优先级最低,表示表达式的开始与结束 循环终止条件:运算符栈中仅剩2个运算符,并且栈顶运算符为")"时,比较优先级应该循环比较 2由于字符串中有空格,而scanf回跳过不处理,认为是下一次输出,因此对于含有空格或制表符的字符串用gets函数,而不是scanf char* gets(char* str); 3打印2位小数用printf("%.2f",i);不是%.2d,这表示整数 %n,mf:即输出总共n位,其中有m位小数,-号是右端补空格 4易错:11看成了2个1,应该以空格为分隔符(截取的字符串),来判定数字
代码:
bool isNumber(char c) { if(' '==c) { return false; } char sNum[11] = {'0','1','2','3','4','5','6','7','8','9'}; int i = 0; while('\0'!=sNum[i]) { //if(c=sNum[i])//关键是==而不是= if(c==sNum[i]) { return true; } i++; } return false; }
bool isOperator(char c) { if(' '==c) { return false; } char sOpe[7] = {'(',')','+','-','*','/'}; int i = 0; while('\0'!=sOpe[i]) { //if(c=sOpe[i]) if(c==sOpe[i]) { return true; } i++; } return false; }
int my_atoi(char c) { switch(c) { case '0':return 0; case '1':return 1; case '2':return 2; case '3':return 3; case '4':return 4; case '5':return 5; case '6':return 6; case '7':return 7; case '8':return 8; case '9':return 9; default: return -999; } return -999; }
int main(int argc,char* argv[]) { //设定优先级,+-一样,()一样,/*一样 map<char,int> mMap; mMap.insert(make_pair<char,int>('(',1)); mMap.insert(make_pair<char,int>(')',1)); mMap.insert(make_pair<char,int>('+',2)); mMap.insert(make_pair<char,int>('-',2)); mMap.insert(make_pair<char,int>('*',3)); mMap.insert(make_pair<char,int>('/',3)); char sInput[201]; //关键是1有空格,计算机就认为你重新输入了,必须扣除这种情况 //while(EOF!=scanf("%s",sInput)) while(gets(sInput)) { stack<char> stackOpe;//运算符栈 //为其添加开始标记( stackOpe.push('('); //stack<int> stackNum;//运算数栈 //易错,由于保留两位小数,数字栈的类型为double stack<double> stackNum; int i = 0; //必须在最后给其添加')' while('\0'!=sInput[i]) { /*如果运算符栈只剩2个元素,并且栈顶为,')',也退出循环 if(2==stackOpe.size() && ')'==stackOpe.top()) { break; } */ bool isNumbe = false; bool isOp = false; if(' '==sInput[i]) { i++; continue; } //获取连续的数字 int retn = 0; if(sInput[i] >= '0' && sInput[i] <= '9') { while(sInput[i] >= '0' && sInput[i] <= '9') { retn *= 10; retn += sInput[i] - '0'; isNumbe = true; i++; } i--;//确保拿到的是数字 } //其余默认为运算符 else { isOp = true; } //数字进数字栈 if(isNumbe || isNumber(sInput[i])) { //stackNum.push(my_atoi(sInput[i])); //stackNum.push(my_atoi(retn)); stackNum.push(retn); i++; //如果这是最后一个数字,则在运算符栈中添加')' if('\0'==sInput[i]) { sInput[i] = ')'; sInput[i+1] = '\0'; } } //运算符进运算符栈 else if(isOp || isOperator(sInput[i])) { //如果栈空,直接压入该运算符 if(stackOpe.empty()) { stackOpe.push(sInput[i]); i++;//计数器累加 } else { //如果当前运算符的优先级比栈顶运算符的优先级要高,则进栈,相同优先级不进栈 map<char,int>::iterator itFind = mMap.find(sInput[i]); if(itFind->second > (mMap.find(stackOpe.top()))->second) { stackOpe.push(sInput[i]); i++; } //如果是栈顶优先级高 >= 当前运算符优先级,则弹出栈顶元素和次栈顶元素,计算结果 1 + 2全部输入完成后,它没有计算,因此需要给定最低的两个运算符 else { double iBackNum = stackNum.top(); stackNum.pop(); double iFrontNum = stackNum.top(); stackNum.pop(); double iRes = 0.0; switch(stackOpe.top()) { case '+': iRes = (iFrontNum + iBackNum)*1.0; break; case '-': iRes = (iFrontNum - iBackNum)*1.0; break; case '*': iRes = (iFrontNum * iBackNum)*1.0; break; case '/': iRes = (iFrontNum / iBackNum)*1.0; break; //如果是"("或者是")" default: break; } //i++;//由于要循环遍历,计数器不变,仍停留在当前运算符,循环与前面的运算符比较 //将计算后的结果压入数字栈 stackNum.push(iRes);
//易错,需要将栈顶高优先级操作符弹出,将当前低优先级操作进栈 stackOpe.pop(); //stackOpe.push(sInput[i]); if('('==stackOpe.top() && ')'==sInput[i]) { break; }
}//else }//else }//else if }//for printf("%.2f\n",stackNum.top()); } system("pause"); getchar(); return 0; } |
3 |
包含min函数的栈: |
剑指offer https://blog.csdn.net/qingyuanluofeng/article/details/39138245 参见经典例题解析 |
4 |
栈的压入、弹出序列 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1、2、3、4、5是某栈的压栈序列,序列4、5、3、2、1是该压栈序列对应的一个弹出序列,但4、3、5、1、2就不可能是该压栈序列的弹出序列。
输入: 每个测试案例包括3行: 第一行为1个整数n(1<=n<=100000),表示序列的长度。 第二行包含n个整数,表示栈的压入顺序。 第三行包含n个整数,表示栈的弹出顺序。 输出: 对应每个测试案例,如果第二个序列是第一个序列的弹出序列输出Yes,否则输出No。 样例输入: 5 1 2 3 4 5 4 5 3 2 1 5 1 2 3 4 5 4 3 5 1 2 样例输出: Yes No |
剑指offer https://blog.csdn.net/qingyuanluofeng/article/details/39138265 关键: 设定一个辅助栈。辅助栈中存放的就是弹出序列,数据栈中: 按照压栈序列,逐个压入元素,当辅助栈的栈顶元素与数据栈的栈顶元素相等时,两者同时弹出栈顶。 然后再比较辅助栈的栈顶是否数据栈的栈顶元素相同,若不相同,再向数据栈内压入元素,直至压入的元素 和辅助栈的栈顶元素相同时,两者再同时弹出。 如果发现辅助栈的栈顶元素,不仅与数 |