3.1栈的类型定义
约定:an为栈顶,a1为栈底
栈:插入只能在n+1的位置(表尾---栈顶)上插入,只能删除n位置上(表尾---栈顶)的元素,栈底是封死的
队列:插入只能在n+1的位置上插入,只能删除第一个元素,两端都是开放的
基本操作:
InitStack(&S)---初始化
DestroyStack(&S)---销毁栈
StackEmpty(S)---判断栈空
StackLength(S)---栈长
GetTop(S,&e)---取得栈顶元素
ClearStack(&S)---将S清为空栈
Push(&S,e)---入栈
Pop(&S,&e)---出栈
3.2栈的应用举例
例一:数制转换
算法基于原理:
N=(N div d)Xd+N mod d
计算过程中得到的数保留,最后从尾到头输出,保留顺序与输出顺序相反----问题本身的后进先出性---栈
∴余数保留入栈,商保留继续循环相除,最后输出
void conversion()
{
InitStack(S);
scanf("%d",N);
while(N)
{
Push(S,N%8);
N=N/8;
}
while(!StackEmpty(S))
{
Pop(S,e);
printf("%d",e);
}
}
例二:括号匹配的检验
检验括号是否匹配的方法用“期待的急迫程度”这个概念来描述
【(【】【】)】-----匹配
挨个扫描,遇到左括号,保留起来,遇到右括号,将最后一个保留的左括号拿出来与右括号匹配,若匹配,继续下一个,若不匹配,不成功
分析可能出现的不匹配的情况:
1.到来的右括号不是所期待的 2.到来的是不速之客 3.直到结束,也没有到来所期待的
算法的设计思想:
1.凡出现左括号,则进栈
2.凡出现右括号,首先检查栈是否空。若栈空,则表明“由括号”多了。否则和栈顶元素比较,若相匹配,则“左括号出栈”,否则不匹配
3.表达式检验结束时,若栈空,则匹配正确,否则表明"左括号"多了
例三:行编辑程序问题
每接受一个字符即存入---不恰当。若出错,就要重来
设立一个输入缓冲区,用以接受用户输入的一行字符,然后逐行存入用户数据区
在用户输入一行的过程中,允许用户输入有差错,并在发现有误时及时更正
假设从终端接受了这样两行字符:
whil##ilr#e(s#*s)------(退格符)---后进先出
outcha@putchar(*s=#++)------(退行符)---将栈清空
则实际有效的是下列两行:
while(*s)
putchar(*s++)
while(ch!=EOF)//EOF为全文结束符
while(ch!=EOF&&ch!='\n')
{
switch(ch)
{
case'#':Pop(S,c);break;
case'@':ClearStack(S);break;
default:Push(S,ch);break;
}
ch=getchar();//从终端接收下一个字符
}
例四:迷宫求解
“穷举求解”----用方格表示迷宫,空白的格子是通的
把迷宫中任何一个位置记为当前位置,到下一位置(四个位置东南西北)进行探索,第一个方向东方向,顺时针探测。南i+1,北i-1,东j+1,西j-1
走过的路径记录---记录坐标,走到不同的位置,往回退,找到别的方位。
求迷宫路径算法的基本思想:
1.若当前位置“可通”,纳入路径,继续前进
2.若当前位置“不可通”,则后退,换向探索
3.若四周均不可通,则从路径中删除
例五:表达式求值
在计算机中,表达式可以有三种不同的标识方法
设 Exp=S1+OP+S2
则称 OP+S1+S2为表达式的前缀表示法。称S1+OP+S2为表达式的中缀表示法。称S1+S2+OP为表达式的后缀表示法。
可见,它以运算符所在不同位置命名的。
aXb+(c-d/e)Xf
前缀式:+XabX-c/def 中缀式:aXb+c-d/eXf -----不允许有括号 后缀式:abXcde/-fX+
结论:(1)运算符的相对次序不同 (2)操作数的相对次数不变 (3)中缀式丢失了括弧信息,致使运算次序不能确定 (4)前缀式的运算规则:连续出现的两个操作数和在它们之前且紧靠它们的运算符构成一个最小表达式
例六:实现递归
当在一个函数的运行期间调用另一个函数时,在运行该被调用函数之前,需要先完成三件事:
1.将所有的实在参数,返回地址等信息传递给被调用函数保存
2.为 被调用函数的局部变量分配存储区
3.将控制转移到被调用函数的入口
从被调用函数返回调用函数之前,应该完成:
1.保存被调用函数的计算结果 2.释放被调用函数的数据区 3.依照被调函数保存的返回地址将控制转移到调用函数
多个函数嵌套调用的规则:后调用先返回此时的内存管理实行“栈式管理”
递归过程指向过程中占用的数据区,称之为递归工作栈,每一层的递归参数合成一个记录,称之为递归工作记录,栈顶记录指示当前层的执行情况,称之为当前活动记录,栈顶指针,称之为当前环境指针。
汉诺塔问题
void hanoi(int n,char x,char y,char z)
//将塔座x上按直径由小到大且之上而下编号为1至n的n个圆盘按规则搬到塔座z上,y可用作辅助塔座
{
if(n==1) move(x,1,z);//将编号为1的圆盘从x移到z
else
{
hanoi(n-1,x,z,y);//将x上编号为1至n-1的圆盘移到y,z做辅助塔
move(x,n,z);//将编号为n的圆盘从x移到z
hanoi(n-1,y,x,z);//将y上编号为1至n-1的圆盘移到z,x做辅助塔
}
}
3.3栈类型的实现
顺序栈
类似于线性表的顺序映象实现,指向表尾的指针可以作为栈顶指针
栈的顺序存储表示
#define STACK_INIT_SIZE 100;
#define STACKINCREMENT 10;
typedef struct
{
SElemType *base;
SElemType *top;
int stacksize;//栈空间的大小
}
与线性表不同的是:线性表是一种描述数据的模型,而栈是一种工具,是从栈空到栈空的状态,所以要有只是栈顶元素的指针
初始化状态:top=base
链栈
3.4队列的类型定义
InitQueue(&Q)---初始化
DestroyQueue(&Q)---销毁队列
QueueEmpty(Q)---判断队列空
QueueLength(Q)---队列长
GetHead(Q,&e)---取得队头元素
ClearQueue(&Q)---将Q清为空队
EnQueue(&Q,e)---入队,从队尾插入
DeQueue(&Q,&e)---删除队头元素,出队
3.5队列类型的实现
链队列---链式映象
队头指针Q.front指向头指针(无数据),第一个元素在头指针后---真正有数据 队尾指针Q.rear指向真正最后一个有数据的元素
对空---队头指针和队尾指着都指向头指针
循环队列----顺序映象
#define MAXQSIZE 100
typedef struct
{
QElemType *base;//动态分配存储空间
int front;//头指针,若队列不空,指向队列头元素
int rear;//尾指针,若队列不空,指向队列尾元素的下一个位置
}
Q.rear=Q.front 队列满或队列空
解决办法:浪费一个空间(Q.rear+1)%MAXQSIZE=Q.front