470 likes | 668 Views
第 4 章 栈和队列. 第 4 章 栈和队列. 4.1 栈 4.2 队列 4.3 小结 4.4 应用举例及分析 习题. 出 栈 入栈. a n. top. ……. a i. ……. a 1. a 0. bottom. 4.1 栈. 4.1.1 栈的定义及基本操作. 定义: 栈( stack ) 是限定在 表的一端 进行插入或删除操作的线性表。 栈顶 (top) : 插入删除操作的一端称为栈顶 ,另一端称为 栈底 (bottom) 。 入栈: 插入元素 出栈: 删除元素 空栈: 不含元素的栈。.
E N D
第4章 栈和队列 4.1 栈 4.2 队列 4.3 小结 4.4 应用举例及分析 习题
出栈 入栈 an top …… ai …… a1 a0 bottom 4.1栈 4.1.1栈的定义及基本操作 定义: 栈(stack)是限定在表的一端进行插入或删除操作的线性表。 栈顶(top):插入删除操作的一端称为栈顶,另一端称为栈底(bottom)。 入栈:插入元素 出栈:删除元素 空栈:不含元素的栈。
出栈 入栈 an …… ai …… a2 a1 栈S 栈S=(a1,a2,a3,…an),栈中元素按a1,a2,a3,…an的次序进栈,则a1为栈底元素,an为栈顶元素。出栈的第一个元素应为栈顶元素,即栈的修改是按后进先出的原则进行的。 栈:后进先出(LIFO)的线性表。
栈常用的几种基本操作: (1)INITSTACK(S) 初始化空栈 (2)EMPTY(S) 判断空栈 (3)PUSH(S) 入栈 (4)POP(S) 出栈 (5)GETTOP(S) 取栈顶元素 (6)CLEAR(S) 栈置空 (7)CURRENT_SIZE(S) 求当前栈中元素个数
顺序栈S 栈顶top an-1 …… ai …… a1 栈底bottom a0 4.1.2栈的顺序存储结构 栈的顺序存储结构简称为顺序栈,利用一组地址连续的存储单元依次存放从栈底到栈顶的数据元素。栈底位置固定不变,设定一个指针top(int型)指向栈顶元素所在的位置 。 an
data an an-1 …… a1 SEQSTACK *s top 顺序栈的数据类型可描述为: #define Datatype1 int #define maxsize 100 typedef struct {Datatype1 data [maxsize] ; int top ; } SEQSTACK
设向量表的0单元为栈底,约定向量中的0单元空闲不用。若MAXSIZE为n,栈中可放入最多元素数为MAXSIZE-1。设向量表的0单元为栈底,约定向量中的0单元空闲不用。若MAXSIZE为n,栈中可放入最多元素数为MAXSIZE-1。 • 若定义 SEQSTACK *s; • s->top= =0 表示栈空 • s->top= =MAXSIZE-1表示栈满 • top指示器是在栈中操作的依据。
5 4 3 2 1 0 S - >top=0 顺序栈基本操作对应算法——(1)初始化空栈 void initstack(SEQSTACK *S) { S->top = 0; } 空栈
顺序栈基本操作对应算法——(2)判栈空 int empty(SEQSTACK *S) { if(S->top == 0) return 1; else return 0; }
5 5 E 5 4 4 D 4 3 3 C 3 x 2 2 B 2 A A 1 1 A 1 0 0 0 S S S - - - >top=2 >top=1 >top=5 入栈前 栈满 入栈后 顺序栈基本操作对应算法——(3)入栈 int push(SEQSTACK *S, DATATYPE1 x) { if(S->top = = MAXSIZE-1) { printf(“Overflow \n"); return 0; } else {S->top ++; (S->data)[S->top] = x; return 1; } }
x 5 5 5 4 D 4 4 3 C C 3 3 2 B B 2 2 1 A A 1 1 0 0 0 S S S - - - >top=0 >top=4 >top=3 空栈 出栈 顺序栈基本操作对应算法——(4)出栈 DATATYPE1 pop(SEQSTACK *s) {DATATYPE1 x; if(empty(s)) {printf (“underflow\n”); x=NULL;} else {x=(s–>data)[s–> top]; s–>top--;} return(x); } D
顺序栈基本操作对应算法——(5)取栈顶元素顺序栈基本操作对应算法——(5)取栈顶元素 DATATYPE1 gettop(SEQSTACK *s) {DATATYPE1 x; if(empty(s)) {printf (“Stack is empty.\n”); x=NULL;} else x=(s–>data)[s–> top]; return(x); }
data next top 栈顶 … 栈底 ^ 4.1.3栈的链式存储结构 用单链表作为存储结构的栈称为链栈。 • top为栈顶指针,是指针型变量,可唯一确定链栈。 • top= =NULL 时,为空栈 • 链栈无栈满问题
data next X LINKSTACK 链栈的数据类型可描述为: #define Datatype2 char typedef struct snode { DATATYPE2 data ; struct snode * next ; } LINKSTACK ;
top p … ^ 链栈基本操作对应算法——(1)入栈 LINKSTACK * pushstack (LINKSTACK * top, DataType2 x) {LINKSTACK * p; p = malloc(sizeof(LINKSTACK)); p->data = x; p->next = top; top = p; return p; } x
top top p … … ^ ^ ^ 空栈 链栈基本操作对应算法——(2)出栈 LINKSTACK * popstack (LINKSTACK * top, DataType2 v) {LINKSTACK * p; if (top= =NULL) printf(“Underflow.\n”); else {v=top->data ; p = top; top = top->next; free(p);} return top; } v x
出队 入队 a1a2…an 4.2 队列 4.2.1队列的定义及基本操作 定义: 队列:是一种线性表,其插入操作均限定在表的一端进行,而删除操作限定在表的另一端进行。所以把队列简称为先进先出的线性表(FIFO)。 队头:允许删除操作的一端称队头(front)。 队尾:允许插入操作的一端称为队尾(rear)。 空队列:当队列中无元素时称为空队列。 队列结构示意图: 队头 队尾
队列的几种基本操作: (1)INITQUEUE(Q) 初始化空队列 (2)EMPTY(Q) 判断空队列 (3)ADDQ(Q,x) 入队列 (4)DELQ(Q) 出队列 (5)GETFRONT(Q) 取队头元素 (6)CLEAR(Q) 队列置空 (7)CURRENT_SIZE(S) 求队列中元素个数
4.2.2队列的顺序存储结构 顺序队列: 定义:顺序存储结构的队列称为顺序队列。 方案:队头队尾各设一个位置指针front和rear(两个指针都是整型变量)。队列中最多存放的元素数为MAXSIZE。队头指针总是指向队列中第一个元素的前一个位置,队尾总是最后一个元素的位置,队列中元素的个数为rear-front。
顺序队列的数据类型描述为: #define DATATYPE1 int #define MAXSIZE 100 typedef struct {datatype data [maxsize] ; int front, rear; } SEQQUEUE; 实现顺序队列基本操作的原则如下: 队列的初始化条件:q->rear=q->front =-1 队满条件:q->rear= MAXSIZE –1 队空条件:q->rear =q->front
5 5 4 4 C C 3 3 B B 2 2 A A 1 1 0 0 顺序队列插入、删除操作示意图: q->rear→ q->rear→ q->front→ q->front→ 初始状态为空 ABC依次出队 ABC依次进队
循环队列: 引入:顺序队列存在这样的问题:已删除元素结点的位置无法利用,甚至存在假满现象。 改进方法:把顺序队列首尾相接构成循环队列,克服假满现象。 循环队列基本操作的原则变为: 队满条件:( q->rear+1 )%MAXSIZE=q->front 队空条件:q->front=q->rear; 循环队列初始化条件:q->rear=q->front=-1
循环队列 顺序队列 0 1 2 3 . . N-1 N-1 0 front 1 a1 a2 front a1 a3 2 rear rear a2 a3 3 顺序循环队列的基本原理:把顺序队列所使用的存储空间构造成一个逻辑上首尾相连的循环队列。当rear和front达到MaxQueueSize-1后,再前进一个位置就自动到0。
循环队列基本操作对应算法——(1)判队空 int empty(SEQQUEUE *q) { if(q->rear == q-> front) return 1; else return 0; }
循环队列基本操作对应算法—— (2)取队头元素 DATATYPE1 getfront(SEQQUEUE *q,) { DATATYPE1 v; if(empty(q)) {printf(“queue is empty.\n”); v=NULL;} else v=(q–data)[(q–>front+1)%MAXSIZE]; return v; }
循环队列基本操作对应算法——(3)入队 int enqueue(SEQQUEUE *q, DATATYPE1 x) {int r; if(q->front==(q->rear+1)%MAXSIZE) { printf(" queue overflow \n"); r=0; } else { q->rear = (q->rear + 1) % MAXSIZE; (q->data)[Q->rear] = x; r=1; } return r; }
循环队列基本操作对应算法——(4)出队 DATATYPE1 dequeue(SEQQUEUE *q) { DATATYPE1 v; if(empty(q)) {printf(“queue is empty.\n”); v=NULL;} else {q–front=(q–>front+1)%MAXSIZE; v= (q->data)[Q->front]; return v; } }
^ 4.2.3 队列的链式存储结构 用链式存储结构表示队列,称为链队列。 一个链队列具有头、尾两个指针。一般设立一个头结点,队列的头指针指向头结点,头结点的下一元素为队头元素。 q->front=q->rear 为队空条件。 链队列无队满问题。 data next q->front 头结点 队头元素 … 队尾元素 q->rear
链队列的数据类型可描述为: #define DATATYPE1 int ; typedef struct qnode {DATATYPE data ; struct qnode * next }LINKQLIST ; typedef struct {LINKQLIST * front , * rear ; } LINKQUEUE ;
链队列基本操作对应算法——(1)队列初始化链队列基本操作对应算法——(1)队列初始化 void initlinkqueue(linkqueue *q) { q–>front=malloc(sizeof(linkqlist)); (q–>front) –>next=NULL; q–>rear= q–>front; }
链队列基本操作对应算法——(2)判断队列空 int emptylinkqueue(linkqueue *q) { int v; if(q–> front= q–>rear) v=1; else v=0; return v; }
链队列基本操作对应算法——(3)读队首元素 DATATYPE1 getlinkqueue(linkqueue *q) { DATATYPE1 v; if(emptylinkqueue(q)) v=NULL; else v=(q–>front) –>next –>data; return v; }
链队列基本操作对应算法——(4)入队列 void enlinkqueue(LINKQUEUE*q,DATATYPE1 x) { (q–>rear) –>next=malloc(sizeof(LINKQLIST)); q–>rear=(q–>rear) –>next; (q–>rear) –>data=x; (q–>rear) –>next=NULL; }
4.3 小结 • 栈和队列是两种特殊的线性表,从逻辑结构上分析,其数据元素仍然是顺序存储的,数据在存储空间的存放地址也是连续的。栈的结构特点是先进后出,数据元素只能在表的一端进行插入和删除。栈指针Top始终指向栈顶元素在栈中的序号。栈的基本算法有进栈和出栈两种,在算法实现中必须注意栈满(上溢)和栈空(下溢)的判断。队列是数据元素在表的一端插入,在表的另一端删除的特殊线性表。队列的结构特点是先进先出。队列有队首指针(也称作退队指针)和队尾指针(也称作进队指针)。
队列的基本算法也有进队和出队两种,在算法中也要注意队满和队空的判断。为了解决队列中的假溢出问题,循环队列是一种有效的队列结构形式,队列中存储单元首尾相接的结构特征能充分利用队列中的存储空间。注意,在循环队列中,队满时始终存在一个空单元。队列的基本算法也有进队和出队两种,在算法中也要注意队满和队空的判断。为了解决队列中的假溢出问题,循环队列是一种有效的队列结构形式,队列中存储单元首尾相接的结构特征能充分利用队列中的存储空间。注意,在循环队列中,队满时始终存在一个空单元。 • 在有序线性表的插入和删除算法中,算法执行在时间上的量级是线性阶O(n)。栈和队列的基本操作算法的时间执行量级是常阶数O(1)。 • 链栈和链队是单链表的一种特殊形式,它们具有链表的存储结构的特征,也有栈和队列操作上的各自特点。
链栈的进栈和退栈的运算都在首部进行,只需调整链栈的入口地址;链队具有进队指针Rear和退队指针Front,进队时调整进队指针Rear的入口地址,退队时调整Front的退队指针。同时,应该注意栈空、队空时的判断。链栈的进栈和退栈的运算都在首部进行,只需调整链栈的入口地址;链队具有进队指针Rear和退队指针Front,进队时调整进队指针Rear的入口地址,退队时调整Front的退队指针。同时,应该注意栈空、队空时的判断。
4.4 应用举例及分析 例1:将一个非负十进制数转换成八进制数,分别用非递归算法和递归算法来实现。 递归算法如下: Void d_to _o(unsigned x) {if(x%8!=0) d_to_or(x/8); Printf(“%d”,x%8); }
非递归算法如下: Void d_to _o(unsigned x) {SEQSTACK stack, *s; S=&stack; Initstack(s); Push(s,’#’); Whiel(x!=0) {push(s,x%8); S=x/8;} While(gettop(s)!=‘#’) Printf(“%d”,pop(s));}}
例2:括号配对的语法处理。 如果表达式中包含有括号“(“和”)”,并可以嵌套使用,则需检测表达式中的括号输入是否匹配/如果是“(())”序列或“(()())”序列,为配对正确,如果出现“(()”序列或“())(”序列为配对不正确。可以基于栈结构设计一个算法来判断输入的一串括号是否配对正确。算法假设输入的字串中除左括号“(“和右括号”)”之外无其他字符,括号串以回车键结束。
算法先分析左括号和右括号配对的规律:左括号和右括号配对一定是先有左括号,后出现右括号。因括号可以嵌套使用,左括号允许单个或连续出现,并等待右括号出现而配对消解。左括号在等待右括号出现的过程中应暂时保存起来,当右括号出现时则一定是和最近出现 的一个左括号配对并消解,当括号出现而找不到有左括号配对时则一定发生了配对不正确的情况。从嵌套的角度考虑,如果左括号连续出现,则后出现的左括号应该与最先来到的右括号配对消解。左括号的这种保存和与右括号的配对消解的过程和栈的后进先出原则是一致的。可以将读到的左括号“(“压入设定的栈中,当读到右括号”)”时就和栈中的左括号配对消解,也就是将栈顶的左括号弹出栈,如果栈顶弹不出左括号,则表示输入括号匹配出错;如果括号串已读完,栈中仍有左括号存在,则也表示输入括号匹配出错。
采用栈结构来检测括号配对是否正确所对应的算法如下:采用栈结构来检测括号配对是否正确所对应的算法如下: int check(SEQSTACK * s) {int bool; char ch; push(s,’# ‘); ch = getchar(); bool = 1; while(ch ! = ‘\n’ && bool) {if(ch = = ‘(‘)) //遇左括号,左括号入栈 push(s,ch); //遇右括号 if(ch = = ’)’) if(gettop(s) = = ‘# ‘) bool = 0; //无左括号配对,则出错 else pop(s); //有左括号配对,则去左括号 ch = getchar(); } if(gettop(s) ! = ‘# ‘) //左括号数目多于右括号,出错 bool = 0; if(bool) printf(“right”); else printf(“error”); }
main() { SEQSTACK st, * s; s = & st; initstack(s); check(s); }
例3:队列管理的模拟算法(队列采用带头结点的链表结构)。例3:队列管理的模拟算法(队列采用带头结点的链表结构)。 采用如下管理模式: • 队列初始化为空队列 • 键盘键入奇数时:奇数从队尾入队列; • 键盘键入偶数时:队头指针指向的奇数出队列; • 键盘键入0时:退出算法; • 每键入一整数,显示操作后队列中的值。
Void outlinkqueue(LINKQUEUE * q) //显示队列中的值 { LINKQLIST * p; p = q->front; printf(“queue :”); While(p ! = q->rear) {p = p->next; printf(%d “ , p->data);} printf(“\n”); }
main() { LINKQUEUE lq, * p; int j; p = &lq initlinkqueue(p); printf(“input a integer :”); scanf(“%d”, &j); While(j ! = 0) { if(j % 2 = = 1) enlinkqueue(p,j); //输入奇数:奇数入队列 else j = delinkqueue(p); //输入偶数:队头奇数队列 outlinkqueue(p); printf(“\n input a integer :”); } }
习题 1、简述栈和线性表的区别. 2、简述栈和队列这两种数据结构的相同点和不同点。 3、将一个非负十进制整数转换成二进制,用非递归和递归算法来实现。