栈的类型定义
栈的类型定义
A D T S t a c k { ADT\ \ Stack\lbrace ADT Stack{
数据对象:
D = { a i ∣ a i ∈ E e l e m S e t , i = 1 , 2 , … , n , n ≥ 0 } D=\lbrace a_i|a_i\in EelemSet,i=1,2,\dots,n,n\geq0\rbrace D={ai∣ai∈EelemSet,i=1,2,…,n,n≥0}
数据关系:
R 1 = { < a i − 1 , a i > ∣ a i − 1 , a i ∈ D , i = 1 , 2 , … , n } R1=\lbrace<a_{i-1},a_i>|a_{i-1},a_i\in D,i=1,2,\dots,n\rbrace R1={<ai−1,ai>∣ai−1,ai∈D,i=1,2,…,n}
约定 a n a_n an端为栈顶, a 1 a_1 a1端为栈底。
基本操作:
} A D T S t a c k \rbrace ADT\ \ Stack }ADT Stack
线性表 | 栈 | 队列 | |
---|---|---|---|
插入 | L i s t I n s e r t ( L , i , e ) ( 1 < = i < = L i s t L e n g t h ( L ) + 1 ) \tt ListInsert(L,i,e) (1<=i<=ListLength(L)+1) ListInsert(L,i,e)(1<=i<=ListLength(L)+1) | I n s e r t ( S , n + 1 , e ) \tt Insert(S,n+1,e) Insert(S,n+1,e) | I n s e r t ( Q , n + 1 , e ) \tt Insert(Q,n+1,e) Insert(Q,n+1,e) |
删除 | L i s t D e l e t e ( L , i , & e ) ( 1 < = i < = L i s t L e n g t h ( L ) ) \tt ListDelete(L,i,\&e)(1<=i<=ListLength(L)) ListDelete(L,i,&e)(1<=i<=ListLength(L)) | D e l e t e ( S , n , & e ) \tt Delete(S,n,\&e) Delete(S,n,&e) | D e l e t e ( Q , 1 , & e ) \tt Delete(Q,1,\&e) Delete(Q,1,&e) |
栈只能在一端插入或删除元素,这一端叫做栈顶,另一端叫做栈底
队列只能在尾端插入,在首端删除
基本操作
- 引用型操作
InitStack(&S); //构造一个空栈S
DestroyStack(&S); //初始条件:栈S已存在 操作结果:栈S被销毁
StackEmpty(S);// 判断栈是否为空
StackLength(S); //返回S的元素个数
GetTop(S,&e); //用e返回S的栈顶元素
- 加工型操作
ClearStack(&S); //将S清为空栈
Push(&S,e); //插入元素e为新的栈顶元素,入栈操作
Pop(&S,&e); //删除S的栈顶元素,并用e返回其值
栈的应用举例
例一、数制转换
算法基于原理: N = ( N d i v d ) × d + N m o d d N=(N\ div\ d)\times d+N\ mod\ d N=(N div d)×d+N mod d
例如: ( 1348 ) 10 = ( 2504 ) 8 (1348)_{10}=(2504)_8 (1348)10=(2504)8,其运算过程如下:
N | N div 8 | N mod 8 |
---|---|---|
348 | 168 | 4 |
168 | 21 | 0 |
21 | 2 | 5 |
2 | 0 | 2 |
代码如下:
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);
}
}//conversion
例二、括号匹配的检验
( [ ] ( ) ) 或 [ ( [ ] [ ] ) ]等为正确的格式。
[ ( ] ) 或 ( [ ( ) ) 或 (()])均为不正确的格式。
检验括号是否匹配的方法用“期待的急迫程度”这个概念来描述。
例如,考虑下面的括号序列:
[ ( [ ] [ ] ) ]
1 2 3 4 5 6 7 8
每出现一个左括号,就等待和他匹配的右括号。最后出现的左括弧期待最高,应最先得到匹配。
左括号出现:进栈
右括号出现:出栈,判断出栈的元素和右括号是否匹配
可能出现的不匹配的情况:
- 到来的右括号不是所期待的;
- 到来的是不速之客(右括号比左括号多)
- 知道结束,也没有到来所期待的
算法的设计思想:
- 凡出现左括弧,则进栈;
- 凡出现右括弧,首先检查栈是否空
若栈空,则表明右括弧多了
否则和栈顶元素相比较,若相匹配,则左括弧出栈,否则不匹配 - 表达式检验结束时,若栈空,则匹配正确,否则表明左括弧多了
代码:(自行完善)
Status matching(String &exp){
int state = 1;
while(i<Length(exp) && state){
switch of exp[i]{
case "(":{Push(S,exp[i]); i++; break;}
case ")":{
if(NOT StackEmpty(S) && GetTop(S)="(")
{Pop(S,e); i++}
else {state = 0;}
break; } ......
}//case
}//switch
}//while
if(StackEmpty(S) && state) return OK;
else return ERROR;
}
例三、行编辑程序问题
设立一个输入缓冲区,用以接受用户输入的一行字符,然后逐行存入用户数据区。
在用户输入一行的过程中,允许用户输入出差错,并在发现有误时可以及时更正。
while(ch != EOF){ //EOF为全文结束符
while(ch != EOF && ch != '\n'){
switch(ch){
case '#':Pop(S,c); break;
case '@'ClesrStack(S); break; //重置S为空栈
default: Push(S,ch); break;
}
ch = getchar(); //从终端接收下一个字符
}
将从栈底到栈顶的字符传送至调用过程中的数据区
ClearStack(S); //重置为空栈
if(ch != EOF) getchar();
}
例四、迷宫求解
通常是用“穷举求解”的方法
- 当前位置应为通路(空白部分)
- 到下一位置进行探索:先向右边走,顺时针探测
- 若当前位置可通,则纳入路径(当前位置和下一步方向),继续前进
- 若当前位置不可通,则后退,换方向探索
- 若四周均不通,则从路径中删除
例五、表达式求值
限于二元运算符的表达式定义:
表达式 ::=(操作数)+(运算符)+(操作数)
操作数::=简单变量|表达式
简单变量::=标识符|无符号整数
表达式在计算机中的表示方法:前缀表示法、中缀表示法、后缀表示法
例如:
E
x
p
=
a
×
b
+
(
c
−
d
/
e
)
×
f
\tt Exp=a\times b +(c-d/e)\times f
Exp=a×b+(c−d/e)×f
- 前缀表示法: + × a b × − c / d e f \tt+\times a\ b \times-c/d\ e\ f +×a b×−c/d e f
- 中缀表示法: E x p = a × b + c − d / e × f \tt Exp=a\times b +c-d/e\times f Exp=a×b+c−d/e×f
- 后缀表示法: a b × c d e / − f × + \tt a\ b\times c\ d\ e/-f\times+ a b×c d e/−f×+
结论:
- 操作数之间的相对次序不变
- 运算符的相对次序不同
- 中缀式丢失了括弧信息,致使运算的次序不确定
- 前缀式的运算规则为:连续出现的两个操作数和在他们之前且紧靠他们的运算符构成一个最小表达式
- 后缀式的运算规则为:运算符在式中出现的顺序恰为表达式的运算顺序;每个运算符和在他之前出现且仅靠他的两个操作数构成一个最小表达式
从后缀式求值:先找运算符,再找操作数
从原表达式求得后缀式:每个运算符的运算次序要由它之后的一个运算符来定,在后缀式中,优先数高的运算符领先于优先数低的运算符。
从原表达式求得后缀式的规律为:
- 设立运算符栈
- 设表达式的结束符为“#”,预设运算符栈的栈底为“#”
- 若当前字符是操作数,则直接发送给后缀式
- 若当前运算符的优先数高于栈顶运算符,则进栈
- 否则,退出栈顶运算符发送给后缀式
- "("对它之前后的运算符起隔离作用,“)”可视为自相应左括弧开始的表达式的结束符
void transform(char suffix[],char exp[]){
InitStack(S); Push(S,'#');
p = exp; ch = *p;
while(!StackEmpty(S)){
if(!IN(ch,OP)) pass(Suffix,ch); //不是运算符集合,放入后缀式
else{
switch(ch){
case '(': Push(S,ch); break;
case ')': {Pop(S,c); while(c!='('){
Pass(Suffix,c);Pop(S,c)
}//while
break;}//case
default:{
while(!Gettop(S,c)&&(precede(c,ch))){
Pass(Suffix,c); Pop(S,c);}
if(ch!='#') Push(S,ch);
break;
} //default
}//switch
}//else
if(ch!='#'){p++; ch=*p}
}//while
}
例六、实现递归
当在一个函数运行期间调用另一个函数时,在运行该被调用函数之前,需完成三件事:
- 将所有的实在参数,返回地址等信息传递给被调用函数保存
- 为被调用函数的局部变量分配存储区
- 将控制转移到被调用函数的入口
从被调用函数返回调用函数之前,应该完成:
- 保存被调用函数的计算结果
- 释放被调用函数的数据区
- 依照被调用函数保存的返回地址将控制转移到调用函数
多个函数嵌套调用的规则是:后调用先返回,内存管理实行栈式管理
递归过程指向过程中占用的数据区,称之为递归工作栈
每一层的递归参数合成一个记录,称之为递归工作记录
栈顶记录指示当前层的执行情况,称之为当前活动记录
栈顶指针,称之为当前环境指针
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作辅助塔
}
}
栈类型的实现
顺序栈
类似于线性表的顺序映像实现,指向表尾的指针可以作为栈顶指针
//栈的顺序存储表示
#define STACK_INIT_SIZE 100;
#define STACKINCREMENT 10;
typedef struct{
SElem Type *base;
SElem Type *top;
int stacksize;
}SqStack;
空栈:top=base
链栈
要注意栈的方向
队列的类型定义
队列的类型定义
A D T Q u e u e { ADT\ \ Queue\lbrace ADT Queue{
数据对象:
D = { a i ∣ a i ∈ E e l e m S e t , i = 1 , 2 , … , n , n ≥ 0 } D=\lbrace a_i|a_i\in EelemSet,i=1,2,\dots,n,n\geq0\rbrace D={ai∣ai∈EelemSet,i=1,2,…,n,n≥0}
数据关系:
R 1 = { < a i − 1 , a i > ∣ a i − 1 , a i ∈ D , i = 1 , 2 , … , n } R1=\lbrace<a_{i-1},a_i>|a_{i-1},a_i\in D,i=1,2,\dots,n\rbrace R1={<ai−1,ai>∣ai−1,ai∈D,i=1,2,…,n}
约定 a 1 a_1 a1端为队列头, a n a_n an端为队列尾。
基本操作:
} A D T Q u e u e \rbrace ADT\ \ Queue }ADT Queue
基本操作
InitQueue(&Q); //初始化
DestroyQueue(&Q); //销毁
QueueEmpty(Q); //判空
QueueLength(Q); //长度
GetHead(Q,&e); //取队头元素
ClearQueue(&Q); //清空队列
EnQueue(&Q,e); //入队
DeQueue(&Q,&e); //出队
队列操作GetHead(Q,&e)
:
- 初始条件:Q为非空队列
- 操作结果:用e返回Q的队头元素
队列操作DeQueue(&Q,&e)
:
- 初始条件:Q为非空队列
- 操作结果:删除队头元素,并用e返回其值
队列操作EnQueue(&Q,e)
:
- 初始条件:队列Q已存在
- 操作结果:插入元素e为Q的新的队尾元素
队列类型的实现
链队列——链式映象
typedef struct QNode{
QElemType data;
struct QNode *next;
}QNode, *QueuePtr;
typedef struct{
QueuePtr front; //队头指针
QueuePtr rear; //队尾指针
}LinkQueue;
循环队列——顺序映像
#define MAXQSIZE 100; //队列最大长度
typedef struct{
QElemType *base; //动态分配存储空间
int front; //头指针,若队列不空,指向队列头元素
int rear; //尾指针,若队列不空,指向队尾元素的下一个位置
}SqQueue;
队列满:(Q.rear+1)%maxsize=Q.rear
队列空:Q.front = Q.rear