620 likes | 780 Views
第三章 栈与队列. 两种重要的,限定操作的线性结构。. 学习要点:. 栈的基本概念、结构特征和操作要点。 栈在顺序存储和链式存储结构下各种操作的实现,栈满与栈空判定条件。 队列基本概念、结构特征和操作要点。 队列在两种存储结构下各种操作如何实现,队满与队空判定条件。 循环队列基本概念,循环队列的队空与队满判定条件。 栈和队列的基本应用和应用要点。. §3.1 栈. 3.1.1 栈的基本概念. 栈的例子 :操作系统中中断现场的保留等。 定义: 限定只能在 表的一端 进行插入或删除操作的线性表。 修改原则 : 后进先出 ( LIFO ). 练习:.
E N D
第三章 栈与队列 两种重要的,限定操作的线性结构。
学习要点: • 栈的基本概念、结构特征和操作要点。 • 栈在顺序存储和链式存储结构下各种操作的实现,栈满与栈空判定条件。 • 队列基本概念、结构特征和操作要点。 • 队列在两种存储结构下各种操作如何实现,队满与队空判定条件。 • 循环队列基本概念,循环队列的队空与队满判定条件。 • 栈和队列的基本应用和应用要点。
§3.1 栈 3.1.1 栈的基本概念 栈的例子:操作系统中中断现场的保留等。 定义:限定只能在表的一端进行插入或删除操作的线性表。 修改原则:后进先出(LIFO)
练习: • 设进栈顺序为1、2、3、4,请说出以下的出栈顺序是否可能? A、1 2 3 4 B、4 3 1 2 C、2 1 4 3 D、3 2 1 4 E、3 4 2 1 F、3 4 1 2 × ×
栈的抽象数据类型: • ADT Stack is • {数据对象:D={ai|ai∈DataType,i=0,1,2,…,n-1,n≥0} • 数据关系:R={<ai-1,ai>|ai-1,ai∈D,i=1,2,…,n-1}约定an-1为栈顶,a0为栈底。 • 基本操作: • (1)Stack InitStack() 初始化并返回一个空栈; • (2)ClearStack (Stack S) 清空栈S中的元素; • (3)int IsEmpty(Stack S) 判断栈是否为空,返回栈空或非空标志; • (4)int IsFull(Stack S) 判断栈是否满,返回栈满或不满标志; • (5)Stack Push(Stack S, DataType x) 若栈未满,将数据元素x入栈; • (6)Stack Pop(Stack S) 若栈非空,删除栈顶元素; • (7)DataType GetTop(Stack S) 若栈非空,返回栈S中的取栈顶元素; • } ADT Stack
3.1.2 栈的顺序存储结构 • 数组定义栈:stack[max] • 规定:top指向栈顶元素所在位置 • 初始:top=-1(空栈) 空栈:top=-1,出栈下溢 栈满:top=max-1,进栈上溢 max-1 … 1 0
3.1.2 栈的顺序存储结构2 (a)空栈 (b)栈中有两个元素 (c)栈满 图3-2 顺序栈各种情况
3.1.2 栈的顺序存储结构3 用C语言定义栈的顺序存储结构如下: #define MAXSIZE 100 /* MAXSIZE指的是栈的最大长度 */ typedef struct {DataType elem[MAXSIZE]; /* 定义数组依次存放栈里的数据元素*/ int top; /* 指向栈顶元素的下标 */ }Stack;
基本操作:顺序栈初始化 算法要求返回一个空的栈,空栈中数据元素个数为0,top值为-1。 00 Stack InitStack() 01 { 02 Stack S; 03 S.top=-1; /* 空栈的top值为-1 */ 04 return(S); 05 }
已知栈S及数据元素x,需要将x放置进栈S 基本操作:进栈 00 Stack Push(Stack S, DataType x) /* 若栈未满,将数据元素x入栈 */ 01 { 02 if (S.top==MAXSIZE-1) 03 printf(“栈已满,无法入栈!”); 04 else 05 { 06 S.top=S.top+1; 07 S.elem[S.top]=x; 08 } 09 return(S); 10 }
基本操作:出栈 00 Stack Pop(Stack S) /* 若栈非空,删除栈顶元素 */ 01 { 02 if (S.top== -1) 03 printf(“栈是空的,无法出栈!”); 04 else 05 S.top=S.top-1; 06 return(S); 07 }
已知栈S,取出栈顶元素 基本操作:取栈顶元素 00 DataType GetTop(Stack S) /* 若栈非空,取栈顶元素赋值给x */ 01 { 02 if (S.top== -1) 03 printf(“栈是空的,无法取栈顶!”); 04 else 05 x=S.elem[S.top]; 06 return(x); 07 }
原理:利用栈只能在栈顶端进行操作的特性,将两个栈的栈底分别设在数组的头和尾,两个栈的栈顶在数组中动态变化,栈1元素比较多时就占用比较多的存储单元,元素少时就让出存储单元供栈2可用,提高了数组的利用率。原理:利用栈只能在栈顶端进行操作的特性,将两个栈的栈底分别设在数组的头和尾,两个栈的栈顶在数组中动态变化,栈1元素比较多时就占用比较多的存储单元,元素少时就让出存储单元供栈2可用,提高了数组的利用率。 多栈共享技术:双端栈 图3-3 双端栈
3.1.3 栈的链式存储结构 规定:top指向栈顶元素地址 链栈中结点结构和单链表一样,指针域指向次顶元素。 用C语言可定义如下: 00 struct node 01 { 02 DataType data; 03 struct node *next; 04 }; 05 typedef struct node StackNode; 06 StackNode *top; 设指针域指向次顶结点
3.1.4 栈的链式存储结构 图3-5 链栈
基本操作:链栈初始化 初始化链栈即建立一个空的链栈,只有top结点,其指针域为空。 00 StackNode *InitStack() 01 { 02 StackNode *top; 03 top=( StackNode *) malloc (sizeof(StackNode)); 04 top->next=NULL; 05 return(top); 06 }
基本操作:进栈 已知一个数据元素x以及链栈top,要求完成x进栈操作。
基本操作:进栈2 00 StackNode * Push(StackNode *top,DataType x) 01 { 02 StackNode *p; 03 p=( StackNode *) malloc (sizeof(StackNode)); /* 申请结点p */ 04 p->data=x; /* x存储到新结点p中 */ 05 p->next=top->next; /* p指向栈顶结点 */ 06 top->next=p; /* top结点指向p,p成为新的栈顶 */ 07 return(top); 08 }
基本操作:出栈2 00 StackNode * Pop(StackNode *top) 01 { 02 StackNode *p; 03 if (top->next==NULL) /* 判断栈是否为空 */ 04 { 05 printf(“栈空,无法出栈!”); 06 return(top); 07 } 08 p=top->next; /* p指向栈顶,待删除 */ 09 top->next=p->next; /* top结点指针域跳过p,指向p的后继 */ 10 free(p); /* 释放p占用的空间 */ 11 return(top); 12 }
3.1.4 栈的应用 1.栈在递归程序中的应用——阶乘运算 递归问题的解决可以大致做这样的算法描述: if (递归结束条件) return (递归结束条件下的返回值); else return (递归计算公式);
3.1.4 栈的应用 1.栈在递归程序中的应用——阶乘运算 解释: ●递归计算公式:是大问题在变成次大问题中的表现出来的规律,要表达清楚这个大问题与下一级的次大问题有什么联系。 ●递归结束条件:解决递归问题中的分解不能无终止地分解下去,需有一个结束的条件。这样才可以由结束递归再返回层层解套,最终解决整个问题。递归的结束条件也称为递归出口。
1 (n=0,1) n!= n*(n-1)! (n>1) 3.1.4 栈的应用 例子:n的阶乘的计算
3.1.4 栈的应用 例子:n的阶乘的计算 00 int fact (int n) 01 { 02 int fac; 03 if (n==0) 04 fac=1; 05 else 06 fac=fact (n-1)*n; 07 return fac; 08 }
3.1.4 栈的应用2 2.栈在递归程序中的应用——Hanoi塔问题 解释Hanoi游戏: (1)设有三根杆子A,B,C。A杆上有n个盘子,从小到大相叠着,编号1—n; (2)每次移动一块盘子到另外一个杆上,但要求每个杆上的盘子只能是小的叠在大的上面; (3)把所有盘子从A杆全部移到C杆上,可利用B杆作为过渡。
3.1.4 栈的应用2 n阶Hanoi递归算法: 00 void Hannoi(int n,char A,char B,char C) 01 { 02 if(n==1) 03 Move(1,A,C); /* 将1号盘从A杆移到C杆 */ 04 else 05 { 06 Hannoi(n-1,A,C,B); /* 将A杆上n-1个盘借助C杆移动到B杆 */ 07 Move(n,A,C); /* 将n号盘从A杆移到C杆 */ 08 Hannoi(n-1,B,A,C); /* 将B杆上n-1个盘借助A杆移动到C杆 */ 09 } 10 }
3.1.4 栈的应用3 n阶Hanoi非递归算法见3.1.4节算法3-11。
3.1.4 栈的应用3 3.栈在括号匹配中的应用 问题理解: 假设一个算术表达式中包含圆括号、方括号和花括号三种类型的括号,编写一个算法,判别表达式中括号是否正确配对。 如“fha{bh(gf[hg]gt)q}gfr”是正确匹配的,“fds[fsd(gfd]g”则缺少“)”。
3.1.4 栈的应用3 3.栈在括号匹配中的应用 程序见3.1.4节算法3-12。 程序设计要点: 顺序扫描被判别的表达式,每当遇到"("、"["或"{"时,将其压入栈中(算法3-12第11-14行);当遇到")"、"]"或"}"时,先检查栈是否是空的,如果是空的,表示缺少对应的左边括号,接着检查当前栈顶元素是否是对应的"("、"["或"{",若是则退栈,否则返回表示不匹配。如对")"的处理(程序中第15-28)。当整个算术表达式检查完毕时,还要对栈的情况进行判断,如栈是空的说明所有左右括号相匹配,否则缺少栈内左括号相对应的右括号(程序中第56-59行)。
θ1θ2 + - * ) + > < > - > > < > \ > > > > ( < < < = 3.1.4 栈的应用4 4.栈在表达式求值中的应用 (1)由计算规则得算符优先关系表 3+5*3 3+5+5 θ1:先来的算符 θ2:后来的算符 >
4.栈在表达式求值中的应用2 (2)原理 两工作栈:一个算符栈,一个操作数栈 a.读取到操作数,直接入操作数栈 b.读取到运算符,取运算符栈栈顶元素与该算符比较优先级: “<”:读取符号入运算符栈; “>”:运算符栈出一元素,操作数栈出两元素, 运算后,结果放回操作数栈; “=”:运算符栈出一元素,与之相抵消。
4.栈在表达式求值中的应用3 计算#3*(15-5)/2#栈的变化过程
4.栈在表达式求值中的应用2 (2)原理 算法结束标志:运算符栈为空,操作数栈有一元素,即为结果。 表达式求值算法见3.1.4节算法3-13。
上机(选做): • 输入十进制数,转换成R进制数。 • 输入一包含“(”和“)”的字符串,检测括号是否匹配(其中括号中能嵌套括号),并输出括号是否匹配的信息(匹配、缺少左括号、缺少右括号);
分析: • gets(fun) • while (fun[i]!=’\0’) • {if (fun[i]==’(‘):入栈; • if (fun[i]==‘)’): • if 栈空:错,缺左边,return; • else 出栈; • i=i+1; • } • 如栈空 对!!! • 否则 错,缺少右边
§3.2 队列 3.2.1 队列基本概念 • 队列的例子:排队等。 定义:限定只能在表的一端进行插入,在表的另一端进行删除的线性表。 修改原则:先进先出(FIFO) 图3-11 队列示意图
队列抽象数据类型: ADT Queue iQ {基本操作: 数据对象:D={ai|ai∈DataType,i=0,1,2,…,n-1,n≥0} 数据关系:R={<ai-1,ai>|ai-1,ai∈D,i=1,2,…,n-1}约定an-1为队尾,a0为队头。 (1)Queue InitQueue() 初始化并返回一个空队列; (2)ClearQueue (Queue Q) 清空队列Q中的元素; (3)int IsEmpty(Queue Q) 判断队列是否为空,返回队列空或非空标志; (4)int IsFull(Queue Q) 判断队列是否满,返回队列满或不满标志; (5)Queue InserQ(Queue Q, DataType x) 若队列未满,将数据元素x入队列; (6)Queue DeleteQ(Queue Q) 若队列非空,删除队头元素; (7)DataType GetHead(Queue Q) 若队列非空,返回队列Q中的队头元素; (8)DataType GetRear(Queue Q) 若队列非空,返回队列Q中的队尾元素; } ADT Queue
3.2.2 顺序队列与循环队列 • 数组定义队列:queue[max] • 规定:front指向队头元素位置 rear指向队尾元素的下一个空位 • 初始:front=rear=0 • 队空:front=rear • 队满:rear=max max-1 … 1 0
3.2.2 顺序队列与循环队列2 (a)空队列 (b)队列中有3个元素 (c)队列3个元素出队 (d)队满 图3-12 顺序队列各种情况
3.2.2 顺序队列与循环队列3 • 假溢出的问题:用循环队列来解决 • 下一个rear计算公式:rear=(rear+1)mod max
3.2.2 顺序队列与循环队列4 • 循环队列队空队满标志冲突的问题: • 以牺牲一个存储空间为代价,当判断到(下一个rear==front)时,即认为队满。 • 实际上,max个空间只存放了(max-1)个元素
3.2.2 顺序队列与循环队列5 • 循环队列队空、队满标志: • 队满:(rear+1)mod max==front • 队空:front=rear 冲突解决!!
练习: • 设循环队列容量为70(序号0~69),现经过一系列的入队和出队后,问下列情况下循环队列中各有几个元素? (1)front=14,rear=21 (2)front=23,rear=12
基于C语言的顺序队列的类型定义: 00 #define MAX 100 01 typedef struct 02 { 03 DataType elem[MAX]; /* 定义数组依次存放队列里的数据元素 */ 04 int front; /* 指向队头元素的下标 */ 05 int rear; /* 指向队尾元素的下一个空位 */ 06 }Queue;
基本操作:初始化队列 00 Queue InitQueue() 01 { 02 Queue Q; 03 Q.front=Q.rear=0; /* 队列初始化时front=rear=0 */ 04 return(Q); 05 }
基本操作:进队列 00 Queue InserQ (Queue Q, DataType x) 01 { 02 if ((Q.rear+1) % MAX== Q.front) 03 printf(“队列已满,无法进队!”); 04 else 05 { 06 Q.elem[Q.rear]=x; /* x进队列 */ 07 Q.rear = (Q.rear+1) % MAX; /* rear指向队尾下一个空位 */ 08 } 09 return(Q); 10 }
基本操作:出队列 00 Queue DeleteQ (Queue Q) /* 若栈非空,删除栈顶元素 */ 01 { 02 if (Q.rear == Q.front) 03 printf(“队列是空的,无法出队!”); 04 else 05 Q.front=(Q.front+1)% MAX; 06 return(Q); 07 }
基本操作:访问队头元素 00 DataType GetHead (Queue Q) /* 若队列非空,取队头元素赋值给x */ 01 { 02 if (Q.rear == Q.front) 03 printf(“队列是空的,无法取队头!”); 04 else 05 x=Q.elem[Q.front]; 06 return(x); 07 }
3.2.3 队列链式存储结构 • 1 struct node • 2 { DataType data; /* 链队列结点数据域类型及名称 */ • 3 struct node *next; /* 指针域类型及名称,指向下一结点 */ • 4 }; • 5 typedef struct node QueueNode; • 6 struct node2 • 7 { QueueNode *front; • 8 QueueNode *rear; • 9 }; • 10 typedef struct node2 Queue;