More Related Content Similar to 第三章 栈和队列(新) (20) More from Wang Yizhe (11) 第三章 栈和队列(新)1. 通常称,栈和队列是限定插入和
删除 只能在表的 “ 端点 ” 进行的线性表。
线性表 栈 队列
Insert(L, i, x) Insert(S, n+1, x) Insert(Q, n+1, x)
1≤i≤n+1
Delete(L, i) Delete(S, n) Delete(Q, 1)
1≤i≤n
栈和队列是两种常用的数据类型
5. 3.1.1 栈的类型定义
栈的定义和特点
定义:限定仅在表尾进行插入或删
除操作的线性表,表尾 — 栈顶,表头
— 栈底,不含元素的空表称空栈。
进栈 出栈
栈 ...
an
顶
……...
栈 s=(a1,a2,……,an)
a2
栈底 a1
特点:先进后出( FILO )或后进先
出( LIFO )
6. 栈的类型定义
ADT Stack {
数据对象:
D = { ai | ai ∈ElemSet, i=1,2,...,n, n≥0 }
数据关系:
R1 = { <ai-1, ai >| ai-1, ai∈D, i=2,...,n }
约定 an 端为栈顶, a1 端为栈底。
基本操作:
} ADT Stack
7. InitStack(&S)
DestroyStack(&S)
StackLength(S)
StackEmpty(s)
GetTop(S, &e)
ClearStack(&S)
Push(&S, e)
Pop(&S, &e)
StackTravers(S, visit())
8. 3.1.2 栈的表示和实现
顺序栈
类似于线性表的顺序映象实
现,指向表尾的指针可以作为栈
顶指针。
//----- 栈的顺序存储表示 -----
#define STACK_INIT_SIZE 100; // 存储空间初始分配量
#define STACKINCREMENT 10;// 存储空间分配增量
typedef struct {
SElemType *base; // 栈底指针
SElemType *top; // 栈顶指针
int stacksize; // 栈的当前可使用的最大容量
} SqStack;
9. 实现:一维数组 s[M]
栈满 栈空
top
5 top F 5 5
4 top E 4 4
3 top D 3 3
top 2
2 top C 2
top B 1
1 top B 1
top top A 0
base 0 top A 0 base
base 出栈
空栈 进栈
栈底指针 b a s e , 始 设数组维数为 M
终指向栈底位置; to p =b a s e , 栈空,此时出栈,
栈顶指针 to p , 其初 则下溢( und e rflo w)
值指向栈底,始终
在栈顶元素的下一
to p =M, 栈满,此时入栈,则
个位置 上溢( o ve rflo w)
10. Status InitStack (SqStack &S)
{// 构造一个空栈 S
S.base=(SElemType*)malloc(STACK_INIT_SIZ
E *sizeof(SElemType));
if (!S.base) exit (OVERFLOW); // 存储分配失败
S.top = S.base;
S.stacksize = STACK_INIT_SIZE;
return OK;
}
11. Status Push (SqStack &S, SElemType e) {
if (S.top - S.base >= S.stacksize) {// 栈满,追加存储空
间
S.base = (SElemType *) realloc ( S.base,
(S.stacksize + STACKINCREMENT) *
sizeof (SElemType));
if (!S.base) exit (OVERFLOW); // 存储分配失
败
S.top = S.base + S.stacksize;
S.stacksize += STACKINCREMENT;
}
*S.top++ = e;
12. Status Pop (SqStack &S, SElemType &e) {
// 若栈不空,则删除 S 的栈顶元素,
// 用 e 返回其值,并返回 OK ;
// 否则返回 ERROR
if (S.top == S.base) return ERROR;
e = *--S.top;
return OK;
}
13. 链栈
栈的链式存储结构。栈顶指针
就是链表的头指针。
栈顶指针
an an-1 a1 ∧
注意 : 链栈
中指针的方
向
14. 入栈操
作
top top
栈底
p x …... ^
p->next=top ; top=p
出栈操
作
q top
栈底
top …... ^
q=top ; top=top->next; //q 返回了出栈的元素
15. §3.2 栈的应用
3.2.1 数制转换
3.2.2 括号匹配的检验
3.2.3 行编辑程序问题
3.2.4 迷宫求解
3.2.5 表达式求值
16. 3.2.1 数制转换
十进制 N 和其他 d 进制数的转换
原理 :
N=( N div d )*d + N mod d
其中: div 为整除运算, mod 为
求余运算
18. 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
19. 3.3.2 括号匹配的检验
假设在表达式中
([]())或[([ ][ ])]
等为正确的格式,
[( ])或([( ))或 (()
] ) 均为不正确的格式。
则 检验括号是否匹配的方法可用
“ 期待的急迫程度 ” 这个概念来描述。
20. 例如:考虑下列括号序列:
[ ( [ ] [ ] ) ]
1 2 34 5 6 7 8
分析可能出现的不匹配的情况 :
到来的右括弧 并非是所 “ 期待
” 的;
直到结束,也没有到来所
“ 期待 ” 的括弧。
22. 3.2.3 行编辑程序问题
不恰
如何实现? 当!
“ 每接受一个字符即存入存储器 ” ?
合理的作法是:
设立一个输入缓冲区,用以接
受用户输入的一行字符,然后逐行
存入用户数据区,并假设 “ #” 为退
格符, “ @” 为退行符。
26. Void LineEdit( ){
// 利用字符栈 S, 从终端接收一行并传送至调用过程的数据
区
InitStack(S); // 构造空栈
ch = getchar(); // 从终端接受一个字符
While(ch!= EOF){ //EOF 为全文结束符
... // 对输入的字符的判断与处理
ClearStack(S); // 重置 S 为空栈
if (ch != EOF) ch = getchar();}
DestroyStack(S);
} //LineEdid
27. while (ch != EOF) { //EOF 为全文结束符
while (ch != EOF && ch != ‘n’) { // ‘ n ’ 为换行符
switch (ch) {
case '#' : Pop(S, c); break;
case '@': ClearStack(S); break;// 重置 S 为空栈
default : Push(S, ch); break;
}
ch = getchar(); // 从终端接收下一个字符
}
将从栈底到栈顶的字符传送至调用过程的
数据区;
28. 3.2.4 迷宫求解
通常用的是“ 穷举求解 ” 的方法
# # # # # # # # # #
# →↓ # $ $ $ # #
# ↓ # $ $ $ # #
# ↓ ←$ $ # # #
# ↓ # # # # #
# →→↓ # # #
# # →→↓ # #
# # # # # ↓ # # #
# →→→Θ #
# # # # # # # # # #
30. “ 下一个位置 ” 指 “ 当前位置 ” 四周 “
东南西北 ” 四个方向上相临的通道块
。
北
西 东
南
当前位置
31. 根据算法思想 , 为了保证在任何
位置上都能沿原路退回 , 需要用一个后
进先出的结构来保存从入口到当前位置
的路径 .
假设以栈 S 记录 “ 当前路径 ” ,则
栈顶中存放的是 “ 当前路径上最后一个
通道块 ” 。由此,“ 纳入路径 ” 的操作即
为 “ 当前位置入栈 ” ;“ 从当前路径上删
除前一通道块 ” 的操作为 “ 出栈 ” 。
32. 算法描述
设定当前位置的初值为入口位置
do {
若当前位置可通
则 { 将当前位置插入栈顶 ; // 纳入路径
若该位置是出口位置 , 则结束 ; // 求得路径存
放在栈中
否则切换当前位置的东相邻方块为新的当前
位置 ;
}
出口 东
当前位置 当前位置 当前位置
33. 算法描述
否则 // 不通
若栈不空且栈顶位置尚有其他方向未探索 ,
则设定新的当前位置为沿顺时针方向旋转找
的栈顶位置的下一相邻块 ;
栈顶位 当前位
置 置
35. 算法描述
说明 : 当前位置可通 , 指未曾走到过的通道
块 , 即要求该通道块既不在当前路径上 , 也
不是曾经纳入过路径的通道块 .
a
起点 a-b-c-f-g ( 简单路径 )
b
d c a-b-c-d-e-c-f-g ( 非简单路径
f ) 若 b 已经纳入过路径 , 则在当
e
前位置为 f 时 , 将不作为可通
终点 g 位置考虑 , 否则 , 将形成死循
环.
36. 通道块的数据描述
Type struct {
int ord; // 通道块在路径上的 “ 序号 ”
PosType seat; // 通道块在迷宫中的 “ 坐标
”
int di; // 从此通道块走向下一通道块的 “ 方
向”
} SElemType; // 栈的元素类型
37. 迷宫问题算法
Status MazePath(MazeType maze,PosType start, PosType
end){
// 若迷宫 maze 中存在从 start 到 end 的通道,则求得一
条存放在栈中,并返回 true ;否则返回 false
InitStack(S); curpos = start; // 设定当前位置为 “ 入口位置
”
curstep = 1; // 探索第一步
do {
...
} while (!StackEmpty(s));
return (false);
} //MazePath
38. 迷宫问题算法
do {
if (Pass(curpos)) { // 当前位置可通
FootPrint(curpos); // 留下足迹
e = ( curstep,curpos,1);
Push(S,e); // 入栈,加入足迹
if (curpos == end) return (ture); // 到达出口
curpos = NextPos(curpos,1);
// 下一个位置为当前位置的东邻
curstep++; // 探索下一个位置
}// if
...
39. 迷宫问题算法
else { // 当前位置不能通过
If ( !StackEmpty(s)) {
Pop(S,e) ; // 出栈,弹出当前的栈顶位置至 e
while(e.di == 4 && !StackEmpty(s)) {
MarkPrint(e.seat); Pop(S,e); // 若该位置无方向可寻,留
下不能通过的标记,并退回一步
} //while
if (e.di < 4) { // 表示仍有方向可寻
e.di++; Push(S,e); // 换下一方向试探
curpos = NextPos(e.seat, e.di);
} //end if
}//end else
42. 算法思想:
设置两个工作栈, OPTR 存运算符
, OPND 存操作数及运算结果。算法运算
思想:
1. 首先置 OPND 栈为空, “ #” 为运算符
栈底元素。
2. 依次读入表达式中的每个字符,若是
操作数进 OPND 栈 , 若是运算符则和
OPTR 栈顶元素比较优先权后作相应操
作,直至整个表达式求值完毕。
43. 例: 3 * ( 7 – 2 ) #
C C C C C C C C
-
2 ( 7-2
5
7 *
3*5
15
3 #
OPND 栈 OPTR 栈
44. 例: 3 * ( 7 – 2 )
OPTR 栈 OPND 栈 输入 操作
1 # 3*(7–2)# PUSH( OPND, ‘3’ )
2 # 3 *(7–2)# PUSH( OPTR, ‘*’ )
3 #* 3 (7–2)# PUSH( OPTR, ‘(’ )
4 #*( 3 7–2)# PUSH( OPND, ‘7’ )
5 #*( 3 7 –2)# PUSH( OPTR, ‘–’ )
6 # * (– 3 7 2)# PHSH( OPND, ‘2’ )
7 # * (– 3 7 2 )# operate( ‘7’,’-’,’2’ )
8 #*( 3 5 )# POP( OPTR )
9 #* 3 5 # operate( ‘3’, ‘*’, ‘5’ )
10 # 15 # return GetTop( OPND )
“(” < “*” , 因此“ ( ” 入操作符栈 OPTR
45. 例: 3 * ( 7 – 2 )
OPTR 栈 OPND 栈 输入 操作
1 # 3*(7–2)# PUSH( OPND, ‘3’ )
2 # 3 *(7–2)# PUSH( OPTR, ‘*’ )
3 #* 3 (7–2)# PUSH( OPTR, ‘(’ )
4 #*( 3 7–2)# PUSH( OPND, ‘7’ )
5 #*( 3 7 –2)# PUSH( OPTR, ‘–’ )
6 # * (– 3 7 2)# PHSH( OPND, ‘2’ )
7 # * (– 3 7 2 )# operate( ‘7’,’-’,’2’ )
8 #*( 3 5 )# POP( OPTR )
9 #* 3 5 # operate( ‘3’, ‘*’, ‘5’ )
10 # 15 # return GetTop( OPND )
“-” >“ )” , 因此执行 7-2 操作,同时
OPTR 栈弹出“ -” , OPND 栈弹出“ 2” 和
46. 例: 3 * ( 7 – 2 )
OPTR 栈 OPND 栈 输入 操作
1 # 3*(7–2)# PUSH( OPND, ‘3’ )
2 # 3 *(7–2)# PUSH( OPTR, ‘*’ )
3 #* 3 (7–2)# PUSH( OPTR, ‘(’ )
4 #*( 3 7–2)# PUSH( OPND, ‘7’ )
5 #*( 3 7 –2)# PUSH( OPTR, ‘–’ )
6 # * (– 3 7 2)# PHSH( OPND, ‘2’ )
7 # * (– 3 7 2 )# operate( ‘7’,’-’,’2’ )
8 #*( 3 5 )# POP( OPTR )
9 #* 3 5 # operate( ‘3’, ‘*’, ‘5’ )
10 # 15 # return GetTop( OPND )
“)” =“(” , 操作符的优先权相等,弹出“ (”,
脱括号并接收下一字符 .
47. OperandType EvaluateExpression() {
// 设 OPTR 和 OPND 分别为运算符栈和运算数栈, OP 为运算符集合。
InitStack (OPTR); Push (OPTR, '#');
initStack (OPND); c = getchar();
while (c!= '#' || GetTop(OPTR)!= '#') {
if (!In(c, OP)) { Push((OPND, c); c = getchar(); }
// 不是运算符则进栈
else
…… O P 为运算符集合,若 c 不
是运算符,则加入到运算
} // while
数栈中
return GetTop(OPND);
} // EvaluateExpression
48. switch ( precede(GetTop(OPTR), c) {
case '<': // 栈顶元素优先权低
Push(OPTR, c); c = getchar();
break;
case '=': // 脱括号并接收下一字符
Pop(OPTR, x); c = getchar();
break;
case '> ': // 退栈并将运算结果入栈
Pop(OPTR, theta);
Pop(OPND, b); Pop(OPND, a);
Push(OPND, Operate(a, theta, b));
break;
} // switch
50. 第三章作业
3.1 设将整数 1 、 2 、 3 、 4 依次进栈,但只要
出栈时栈非空,则可将出栈操作按任何次序夹
入其中,请回答下列问题:
( 1 )若入栈次序为
push(1) , pop() , push(2 ), push(3) , pop()
, pop( ) , push(4) , pop( ) ,则出栈的数字序
列为什么?
3.2 写出检验括号匹配的算法。
53. 队列的类型定义
ADT Queue {
数据对象:
D = {ai | ai∈ ElemSet, i=1,2,...,n, n≥0}
数据关系:
R1 = { <a i-1,ai > | ai-1, ai ∈ D, i=2,...,n}
约定其中 a1 端为队列头 , an 端为队列尾
基本操作:
} ADT Queue
54. 队列的基本操作:
InitQueue(&Q) DestroyQueue(&Q)
QueueEmpty(Q) QueueLength(Q)
GetHead(Q, &e) ClearQueue(&Q)
EnQueue(&Q, e) DeQueue(&Q, &e)
QueueTravers(Q, visit())
55. 3.4.2 链队列-队列的链式表示和
实现
typedef struct QNode{// 结点类型
QElemType data ;
struct QNode *next ;
}QNode, *QueuePtr;
typedef struct{ // 链队列类型
QueuePtr front ; // 队头指针
QueuePtr rear ; // 队尾指针
} LinkQueue;
56. Q.front a1 … an ∧
Q.rear
Q.front
空队 ∧
Q.rear
列
57. 空
队 ^ Q.rear -> next=p
front rear p Q.rear=p
x 入队 x ^
front rear p
y 入队 x y ^
front rear
p
x 出队 x y ^
front rear
y 出队 ^ p= Q.front -> next
Q.front -> next = p -> next
front ear
r
58. Status InitQueue (LinkQueue &Q) {
// 构造一个空队列 Q
Q.front = Q.rear =
(QueuePtr)malloc(sizeof(QNode));
if (!Q.front) exit (OVERFLOW);
// 存储分配失败
Q.front->next = NULL;
return OK;
}
59. Status EnQueue (LinkQueue &Q,
QElemType e) {
// 插入元素 e 为 Q 的新的队尾元素
p = (QueuePtr) malloc (sizeof (QNode));
if (!p) exit (OVERFLOW); // 存储分配失败
p->data = e; p->next = NULL;
Q.rear->next = p; Q.rear = p;
return OK;
}
60. EnQueue p
... ∧ e ∧
Q.front Q.rear Q.rear
p->data = e; p->next = NULL;
Q.rear->next = p; Q.rear = p;
61. Status DeQueue (LinkQueue &Q,
QElemType &e) {
// 若队列不空,则删除 Q 的队头元素,
// 用 e 返回其值,并返回 OK ;否则返回
ERROR
if (Q.front == Q.rear) return ERROR;
p = Q.front->next; e = p->data;
Q.front->next = p->next;
if (Q.rear == p) Q.rear = Q.front;
free (p); return OK;
62. DeQueue
p
e ... ∧
Q.front Q.rear
p = Q.front->next; e = p->data;
Q.front->next = p->next;
if (Q.rear == p) Q.rear = Q.front;
free (p);
64. 实现:用一维数组实现 sq[M]
rear
5 5 5 J6 5
4 4 4 J5 4
rear rear J4
3 3 3 3
rear
2 J3 2 front J3 2 front 2
rear front J2
1 J2 1 front 1 1
rear=0 rear
0 front J1 0 front J1 0 0
front=0 J4,J5,J6 入
空队列 J1,J1,J3 入队 J1,J2,J3 出队 队
存在问题:
当 front=0,rear=M 时再有元素入队发生溢出 — — 真
溢出
当 front≠0,rear=M 时再有元素入队发生溢出 — — 假
65. 解决方案 1
队首固定,每次出队剩余元素向下移
动 , 避免发生假溢出 .
Rear Rear
J6 J6
J5 J5 Rear J6
J4 J4 J5
J3 J3 J4
front J2 front front J3
方法可行 , 但将花费太多的时间
66. 解决方案 2 循环队列
基本思想:把队列设想成环形,让
sq[0] 接在 sq[M-1] 之后,若 rear+1==M,
则令 rear=0;
M-1 J6 Rear M-1 J6
... ... ... ...
2 J5 2 J5
1 J4 Front 1 J4 Front
0 0 J7 Rear
rear+1 = M, 于是令 rear = 0, “J7” 入队 ,
有 sq[rear] = J7. 能够避免假溢出 .
67. 解决方案 2
循环队列
实现: 利用 “ 模 ” 运算,实现若 rear+1==M,
则 rear=0 的设定 ;
入队: sq[rear]=x;
rear=(rear+1)%M;
出队: x=sq[front];
front=(front+1)%M;
队满、队空判定条件
68. front
队空: fro nt==re a r rear
队满: fro nt==re a r
4 5 0
3
2 1
rear
J6
J5 4 5 0
3 J4,J5 ,J6 出 队 J6
2 1
J4 J7,J8
,J9 入 J5 4 5 0 J7
front
队 3 2 1 J8
J4
初始状态 front J9
rear
队满队空如何区分判定 ?
70. 分析可见 , 在 C 语言中不能用
动态分配的一维数组来实现循环队
列 . 如果用户的应用程序中设有循环
队列 , 则必须为它设定一个最大队列
长度 ; 若用户无法预估所用队列的
长度 , 则宜用链队列 .
71. Status InitQueue (SqQueue &Q) {
// 构造一个空队列 Q
Q.base = (QElemType *) malloc
(MAXQSIZE *sizeof (QElemType));
if (!Q.base) exit (OVERFLOW);
// 存储分配失败
Q.front = Q.rear = 0;
return OK;
}
72. Status EnQueue (SqQueue &Q, QElemType
e) { // 插入元素 e 为 Q 的新的队尾元素
if ((Q.rear+1) % MAXQSIZE == Q.front)
return ERROR; // 队列满
Q.base[Q.rear] = e;
Q.rear = (Q.rear+1) % MAXQSIZE;
return OK;
}
73. Status DeQueue (SqQueue &Q, QElemType &e)
{ // 若队列不空,则删除 Q 的队头元素,
// 用 e 返回其值,并返回 OK; 否则返回 ERROR
if (Q.front == Q.rear) return ERROR; // 队空
e = Q.base[Q.front];
Q.front = (Q.front+1) % MAXQSIZE;
return OK;
}
74. 例 . 写出以下程序段的输出结果(队列中的
元素
类型 QElemType 为 char )。
Void main( ){
Queue Q; InitQueue(Q);
Char x=‘e’, y=‘c’;
EnQueue(Q, ‘h’); EnQueue(Q, ‘r’);
EnQueue(Q, y);
DeQueue(Q, x); EnQueue(Q, x);
DeQueue(Q, x); EnQueue(Q, ‘a’);
While ( !QueueEmpty(Q) ){
DeQueue(Q, y); Printf(y);
}
Printf(x);
}
76. 括号匹配的检验
Status check( ) { // 对于输入的任意一个字符串 , 检
验括号是否配对
SqStack s;
SElemType ch[80], *p,e; //c h 存放字符串
InitStack(s); // 初始化栈成功
Gets(ch); // 输入字符串
p = ch; //p 指向字符串的首字符
... // 输入字符串中括号的检验
77. 括号匹配的检验
while(*p) // 没有到串尾
Switch(*p){
case ‘(’, ‘[’ , ‘{’ :
Push(s,*p++); break; // 左括号入栈且 p++
case ‘)’, ‘]’ , ‘}’ :
if (!StackEmpty(s)) { // 若栈不空
Pop(s,e); // 弹出栈顶元素
if (!(e==‘(’&& *p ==‘)’ ||
e==‘[’&& *p ==‘]’|| e==‘{’&& *p ==‘} ’) // 出现
了 3 种匹配情况之外的情况
78. 括号匹配的检验
{ printf(“ 左右括号不匹配 n”);
return ERROR;
}
p++;
}
else {// 栈空
printf( “ 缺乏左括号 n”);
return ERROR;
} //end if
default: p++; // 其它字符不处理,指针向后移
} //end switch