基本概念
栈的定义 | 限定仅在表尾进行插入或删除的线性表 | |
组成 | 栈顶 | |
栈底 | ||
基本操作 | 入栈(PUSH) | 往栈中插入一个元素 |
弹栈(POP) | 从栈顶删除一个元素 | |
栈的表示 | 顺序栈 | |
链栈 |
对于顺序栈,有两个指针base和top
base指向栈底
top指向栈顶
top指向栈顶
对于栈的一些基本情况:
栈不存在时候 | base=NULL |
栈为空时 | top=base |
栈的长度 | top-base |
链栈略过。
栈的应用
1 数制转换
数制转换我们使用一种称之为“
辗转相除法”的算法。此算法的基本原理基于:
N=(N div d)*d + N mod d
应用辗转相除法,我们看看如何将十进制的1348转换成八进制:
在程序上,我们将N mod 8的结果入栈,在div结束之后弹栈得到答案就是2508了。
最后我们看一下程序的实现,很简单,程序分为两个流程:
入栈过程
弹栈过程
弹栈过程
入栈过程也就是运算过程,使用Java风格伪代码:
Stack<int> stack=new Stack<int>;
while(N!=0)
{
stack.push(N%8);
N=N/8;
}
while(N!=0)
{
stack.push(N%8);
N=N/8;
}
弹栈过程就是输出八进制数的时候了:
String result;
while(!stack.empty())
{
result+=stack.pop();
}
while(!stack.empty())
{
result+=stack.pop();
}
2 括号匹配检验
栈的另个用途就是检测括号的匹配,算法的思路是:
1.构造一个空栈,栈的元素是单个字符 |
2.对于括号,我们分为"("、")"、"["、"]"、"{"、"}"这六种,其中两两配对,每对都有左右两种,我们遍历整个全是括号的字符串,针对3种左括号,我们遇到之后将它入栈 |
3.针对于遇到的右括号,我们弹出栈顶元素,看看弹出的左括号是不是能和遇到的右括号匹配,如果能继续往后遍历字符串,反之检验失败:括号不能匹配 |
4.当我们遍历整个字符串没有发现3中的检验失败,我们要做一个收尾工作:如果现在栈为空则检验括号是匹配的;反之表示多出了不能匹配的左括号,括号不能匹配 |
针对能和不能匹配两种情况,我们举个例子说明:
能匹配的情况:
[([]())]
不能匹配的情况:
[([]()})]
算法的伪代码:
boolean check(String str)
{
Stack<char> stack=new Stack<char>();
for(int i=0;i<str.length;++i)
{
if(str[i].equal("(")||str[i].equal("[")||str[i].equal("{")) //左括号
{
stack.push(str[i]);
}
else //右括号
{
if(str[i].equal(")"))
{
if(!"(".equal(stack.pop()))
return false;
}
else if(str[i].equal("]"))
{
if(!"[".equal(stack.pop()))
return false;
}
else if(str[i].equal("}"))
{
if(!"{".equal(stack.pop()))
return false;
}
}
}
if(stack.empty())
return true;
return false;
}
{
Stack<char> stack=new Stack<char>();
for(int i=0;i<str.length;++i)
{
if(str[i].equal("(")||str[i].equal("[")||str[i].equal("{")) //左括号
{
stack.push(str[i]);
}
else //右括号
{
if(str[i].equal(")"))
{
if(!"(".equal(stack.pop()))
return false;
}
else if(str[i].equal("]"))
{
if(!"[".equal(stack.pop()))
return false;
}
else if(str[i].equal("}"))
{
if(!"{".equal(stack.pop()))
return false;
}
}
}
if(stack.empty())
return true;
return false;
}
3 行编辑程序
见严书P49。
4 迷宫求解
针对严书上的迷宫求解,我不甚明了,所谓迷宫求解也就是搜索算法,也可以认为是图论的一部分,而搜索算法分为
广度优先搜索算法
深度优先搜索算法
深度优先搜索算法
针对深度优先搜索算法,由于需要回溯,所以经常使用栈去记录路径和状态,这个按下不表。
5 表达式求解
我们使用“
算符优先法
”去对表达式求值
我们的表达式求值只包括加减乘除括号这几种操作,针对于两个算符a b,两者的关系只能是
a<b | a的算数优先级小于b的算数优先级 |
a>b | a的算数优先级大于b的算数优先级 |
a=b | a的算数优先级等于b的算数优先级 |
为了方便,我们添加了“
#
”这个操作符作为表达式的开始和结束,各算符优先权如下
下面谈算法的实现
[1] 首先我们需要构造两个栈
OPTR | 存放操作符 |
OPND | 存放操作数和运算结果 |
[2] 算法步骤是
1 初始情况 | OPND为空 | ||
OPTR中栈底为# | |||
2 依次读入每个字符c | 如果c是操作数 | OPND.push(c) | |
如果c是操作符,将OPTR栈顶操作符OPTR.top()和它比较 | c<OPTR.top() | operation=OPTR.pop(); | |
a=OPND.pop(); | |||
b=OPND.pop(); | |||
Cal(a,opreation,b); | |||
c=OPTR.top() | OPTR.pop() | ||
c>OPTR.top() | OPTR.push(c) | ||
3 获取结果 | result=OPND.top() |
下面我们用一个栗子看一下算法中栈的状态变化
计算3*(7-2)
相应的栈的操作:
相关算法的实现以后会补齐。
6 栈和递归
递归是程序设计上一个非常重要的概念,关于递归最精炼的描述是一个冷笑话:
”
要想知道什么是递归,那么你首先得知道什么是递归。
“
要想知道什么是递归,那么你首先得知道什么是递归。
“
理解递归的人看到这个笑话一定会会心一笑。
关于递归的基础知识这里就不多说了。递归有很多应用,如求斐波那契数列、求阶乘....
下面问题是:如何将一个递归的算法改成非递归的实现?
这个问题是有非常广阔的前景的,学习数据结构后面的知识大家会知道树的深度优先搜索的递归和非递归形式。事实上,所以递归算法都会有其非递归的实现,因为递归的函数调用是和栈息息相关的,那么,如果我们自己构造一个栈去模拟递归就可以得到非递归的实现了。
汉诺塔问题
[待完善]