数据结构第3章栈和队.ppt_第1页
数据结构第3章栈和队.ppt_第2页
数据结构第3章栈和队.ppt_第3页
数据结构第3章栈和队.ppt_第4页
数据结构第3章栈和队.ppt_第5页
已阅读5页,还剩61页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第3章 栈和队列,本章主要内容,栈的概念、存储结构及其基本操作 栈的应用 栈与递归 队列的概念、存储结构及其基本操作 队列的应用,3.1 栈,栈的定义 栈是一种特殊的线性表。其特殊性在于限定插入和删除数据元素的操作只能在线性表的一端进行。 进行插入和删除的称为栈顶,并用一个“栈顶指针”指示;而另一端是固定端,称为栈底。,图 3-1,栈的操作入栈演示,a,b,c,单击标题开始演示!,下一页,栈的操作出栈演示,a,b,c,单击标题开始演示!,下一页,栈的特点,后进先出(Last In First Out),简称为LIFO线性表。 举例1:家里吃饭的碗,通常在洗干净后一个一个地落在一起存放,在使用时,若一个一个地拿,一定最先拿走最上面的那只碗,而最后拿出最下面的那只碗。 举例2:在建筑工地上,使用的砖块从底往上一层一层地码放,在使用时,将从最上面一层一层地拿取。,栈的基本操作,1、栈初始化:Init_Stack(S) 初始条件:栈S不存在 操作结果:构造了一个空栈。 2、销毁栈:Destroy_Stack(S) 初始条件:栈S已存在 操作结果:销毁一个已存在的栈。 3、判栈空:Empty_Stack(S) 操作结果:若S为空栈返回为1,否则返回为0。,栈的基本操作,4、入栈: Push_Stack(S,x) 初始条件:栈S已存在 操作结果:在栈s的顶部插入一个新元素x, x成为新的栈顶元素。栈发生变化 5、出栈:Pop_Stack(S) 初始条件:栈S存在且非空 操作结果:栈S的顶部元素从栈中删除,栈中少了一个元素。栈发生变化 6、取栈顶元素:GetTop_Stack(S) 初始条件:栈S存在且非空 操作结果:栈顶元素作为结果返回,栈不变化,栈的顺序存储,栈的顺序存储结构是用一组连续的存储单元依次存放栈中的每个数据元素,并用起始端作为栈底。 类型定义如下所示: #define MAXSIZE 100 typedef struct DataType dataMAXSIZE; int top; SeqStack, *PSeqStack; 定义一个指向顺序栈的指针: PSeqStack S; S=( PSeqStack)malloc(sizeof(SeqStack);,1、初始化栈,PSeqStack Init_SeqStack(void) /*创建一顺序栈,入口参数无,返回一个指向顺 序栈的指针,为零表示分配空间失败*/ PSeqStack S; S=( PSeqStack)malloc(sizeof(SeqStack); if (S) S-top= -1; return S; ,初始化后内存的存储映象如下页所示,顺序存储的栈的基本操作算法,2、销毁栈 顺序栈的销毁操作是初始化操作的逆运算。由于要修改栈的指针变量,所以要将指针地址传给该函数。 void Destroy_ SeqStack (PSeqStack * SeqStackPoint) /*销毁顺序栈,入口参数:为要销毁的顺序栈指针地址*/ if (*SeqStackPoint) free (*SeqStackPoint) ; * SeqStackPoint =NULL; return ; ,顺序存储的栈的基本操作算法,main() PSeqStack S; S=Init_SeqStack(); Destroy_ SeqStack ( ,顺序存储的栈的基本操作算法,顺序存储的栈的基本操作算法,3、判栈空 判断栈中是否有元素,只需判断top是否等于-1 int Empty_SeqStack(PSeqStack S) /*判断栈是否为空,入口参数:顺序栈,返回值:1表示为空,0表示非空*/ if (S-top= = -1) return 1; else return 0; ,4、入栈 入栈操作是在栈的顶部进行插入一个元素。首先判断栈是否已满,若满退出,否则,由于栈的top指向栈顶,只要将入栈元素赋到top+1的位置同时top+即可,int Push_SeqStack (PSeqStack S, DataType x) /*入口参数:顺序栈,返回值:1表示入栈成功,0 表示失败。*/ if (S-top= =MAXSIZE-1) return 0; /*栈满不能入栈*/ else S-top+; S-dataS-top=x; return 1; ,顺序存储的栈的基本操作算法,5、出栈 出栈操作是在栈的顶部进行删除操作,首先判断栈是否为空,若空退出,否则,由于栈的top指向 栈顶,只要修改top为top -1即可。,int Pop_SeqStack(PSeqStack S , DataType *x) /*入口参数:顺序栈,返回值:1表示出栈成功,0表示失败。*/ if (Empty_SeqStack ( S ) ) return 0; /*栈空不能出栈 */ else *x= S-dataS-top; S-top-; return 1; ,顺序存储的栈的基本操作算法,6、取栈顶元素 取栈顶元素操作是取出栈顶指针top所指的元素值。先判断栈是否为空,若空退出,否则返回top 所指单元的值,int GetTop_SeqStack(PSeqStack S, DataType *x) /*取出栈顶元素, 入口参数:顺序栈,被取出的元素指针,这里用 指针带出栈顶值返回值:1表示成功,0表示失败。*/ if ( Empty_SeqStack ( S ) ) return 0; /*栈空*/ else *x= S-dataS-top; /*栈顶元素存入*x中*/ return (1); ,顺序存储的栈的基本操作算法,若是栈中元素的数目变化范围较大或不清楚栈元素的数目,就应该考虑使用链式存储结构 “链栈”。链栈通常用一个无头结点的单链表表示。 对于单链表,在首端插入删除结点要比尾端相对地容易一些,所以,将单链表的首端作为栈顶端,即将头指针作为栈顶指针。,栈的链式存储,栈的链式存储结构C定义,定义栈的节点元素: typedef struct node DataType data; struct node *next; StackNode, *PStackNode; 定义栈: typedef struct PStackNode top; LinkStack, *PLinkStack; PLinkStack S; S=( PLinkStack)malloc(sizeof(LinkStack);,1、初始化空栈 PLinkStack Init_LinkStack(void) /*初始化链栈,入口参数:空,返回值: 链栈指针,null表示初始化失败*/ PLinkStack S; S=(PLinkStack)malloc(sizeof(LinkStack); if (S) S-top=null; return (S); ,链栈各项基本操作的算法,2、销毁栈 void Destroy_LinkStack (PLinkStack *LS) /*销毁链栈,入口参数:为要销毁的链栈指针地址,无返回值*/ PStackNode p,q ; if (*LS) p=*LS-top; while(p) q=p; p=p-next; free(q); free (*LS) ; * LS=null; ,链栈各项基本操作的算法,3、判栈空 int Empty_LinkStack(PLinkStack S ) /*判断链栈是否为空,入口参数:链栈指针,返回值: 1表示栈空,0表示栈非空*/ return (S-top=null) ; ,链栈各项基本操作的算法,4、入栈 int LinkStack Push_LinkStack(PLinkStack S, DataType x) PStackNode p ; p=(PStackNode) malloc(sizeof(StackNode)); if (!p) printf(“内存溢出”); return (0); p-data=x; p-next=S-top; S-top=p; return (1); ,链栈各项基本操作的算法,5、出栈 int Pop_LinkStack (PLinkStack S, DataType *x) PStackNode p; if (Empty_LinkStack (S) printf(“栈空,不能出栈”); return (0); *x =S- top-data; p =S- top; S-top = S-top-next; free (p); return (1); ,链栈各项基本操作的算法,6、取栈顶元素 int GetTop_LinkStack (PLinkStack S, DataType *x) /*返回值:1表示出栈成功,0表示失败*/ if (Empty_LinkStack (S) printf(“栈空”); return (0); *x =S- top-data; return (1); ,返回,链栈各项基本操作的算法,3.2 栈的应用举例,数制转换问题 将十进制数N转换为r进制的数,其转换方法利用辗转相除法 算法思想如下: (1)初始化栈,初始化N为要转换的数,r为进制数 (2)判断N的值,为0转(4),否则N % r 压入栈s中 (3)用N / r 代替 N转(2) (4)出栈,出栈序列即为结果,数制转换算法,typedef int DataType; int conversion(int N,int r) PSeqStack S; DataType x; /*定义一个顺序栈*/ if (!r) printf(“基数不能为0”); return(0); S=Init_SeqStack(); /*初始化栈*/ if (!S) printf(“栈初始化失败”); return(0); while ( N ) Push_SeqStack ( S ,N % r ); /*余数入栈 */ N=N / r ; /* 商作为被除数继续 */ while ( !Empty_SeqStack(S) ) /*直到栈空退出循环 */ Pop_SeqStack (S , /*销毁栈 */ ,3.3 栈与递归,递归定义 递归是程序设计中最常用的设计方法之一 递归的定义:若一个对象部分地包括它自己,或用它自己给自己定义,则称这个对象是递归的 递归分为直接递归和间接递归 递归的程序思路清晰,编程简化,递归的例子,求n的阶乘:-数值计算 int fact(int n) if (n=1) return 1; else return (n*fact(n-1); 汉诺塔问题:-非数值求解 void Hanio(int n ,char a,char b,char c) if (n=1) move(n, a, c); else Hanio(n-1,a,c,b); move(n, a, c); Hanio(n-1, b,a,c); ,非数值计算的递归,对数值计算而言:有递推公式的可直接写成递归算法如:求xn、费波那契数列、阿克曼函数等 对非数值问题而言:如果问题是递归定义的可用递归算法,如广义表、树等,但具有递归定义的问题毕竟有限,对于一般问题求解,如果具有下列三个性质,就能采用递归算法。 1)大问题能分解成若干个子问题; 2)子问题或者是一个定值(直接解)或者是与 大问题具有同样性质的问题; 3)子问题经过不断分解在最小尺度上有直接 解,即分解过程最终能结束也就是递归有结 束条件。,递归过程的调用和返回,递归函数的递归过程:在递归进层(ii +1层)时系统需要做三件事: 保留本层参数与返回地址(将所有的实在参 数、返回地址等信息传递给被调用函数保存); 给下层参数赋值(为被调用函数的局部变量 分配存储区); 将程序转移到被调函数的入口。,递归过程的调用和返回,从被调用函数返回调用函数之前,递归退层(ii +1层)系统也应完成三件工作: 保存被调函数的计算结果; 恢复上层参数(释放被调函数的数据区); 依照被调函数保存的返回地址,将控制转 移回调用函数。,递归函数的非递归化(略),递归算法具有两个特性: 递归算法是一种分而治之、把复杂问题分 解为简单问题的求解方法,对求解某些复杂 问题,递归算法是有效的。 递归算法的时间效率差,因为需要不断地 进栈和出栈。人们常常将递归改写成非递归, 可机械地改写。,递归设计举例,1)用递归写出求数组中n个元素之和 求n个元素的和与求n1个元素的和 是相同的过程,int sum(int a,int n) /*求数组a中的n个元素之和*/ if(n=0) /*最小尺度当n=0,数组a中无元素,和为0*/ return 0; else return(sum(a,n-1)+an-1); /*否则应该是数组中的第n个元素和n1个元素之和*/ ,递归设计举例 2)用递归写出全排列问题 设R=r1,r2,,rn是要进行排列的n个元素,集合X中元素的全排列记为Perm(X) Ri=R-ri 。 (ri) Perm(X)表示在全排列Perm(X)的每一个排列前加上前缀ri得到的排列 则:R的全排列可归纳定义如下:,当n=1时,Perm( R ) = ( r ) , 其中r是集合R中惟一的元素; 当n1时,Perm( R ) 由 (r1)Perm(R1), (r2)Perm(R2),(rn)Perm(Rn) 构成。,全排列算法,产生Perm( R ) 的递归算法如下,设R为一整型数组int listN void Perm( int list, int k, int m) /*求下标k 至m元素的全排列*/ int i; if(km) for( i=0; i=m; i+) printf(“%d“, listi); printf(“n“); else for(i=k; i=m; i+) Swap( list , k, i ); /*依次将(ri)移至待排数组第一位置*/ Perm(list, k+1,m); /*递归求Perm(Ri),构成(ri) Perm(Ri),*/ Swap( list , k , i ); /*将(ri)换回原位置*/ ,递归设计举例,3)用递归写出字符串倒置的算法 分析:将字符串倒置,可以将第一个元素和最后一个元素调换,再把剩下的字符串倒置,而剩下的字符串长度就在原来的长度上减2,如果字符串的串长1则无需倒置直接返回,字符串倒置算法,void ConverseStrt( char *Str,int start ,int end) /* 将字符串倒置,Str为字符串,strat和end为字符数 组的首尾下标*/ if (end-start Strend /*交换字符*/ ConverseStrt (Str , start+1 , end-1); /*Str的串长1,字符串的首尾元素调换,再将去掉 首尾元素的字符串调换*/ ,递归设计举例,4)火车进站问题 编号为A1,A2,A3,, An (A1 A2A3 An) 的 N 列火车顺序开进一个栈式结构的站台,如图3-11所示,问N列火车的出站有多少种可能?,火车进站问题,分析:进站过程中,火车的动作有两种,进站和出站。设某一时刻站台入口处有i列火车,站台内有j列火车,则下一动作有两种可能: (1) 站台入口处第1列火车入站台。 (2) 站台内最顶上的火车出站台。,用递归函数,设计对应火车进站问题的递归描述函数: F(0,j)=1,F(0,0)=1,j0 (1) F(i,0)=F(i-1,1),i0 (2) F(i,j)=F(i-1,j+1)+F(i,j-1),i0,j0 (3) 其中: F(i,j)表示站台外有i列火车,站台内有j列火车 F(0,j)表示站台外没有火车,站台内有j列火车 F(i,0)表示站台内没有火车,有i列火车在站台外,火车进站问题,int Train_into_PlatForm(int i , int j) if(i=0) return 1; /*火车没有,或者全部在站台内*/ else if(j=0) return Train_into_PlatForm(i-1,1); else return Train_into_PlatForm(i-1,j+1)+Train_into_PlatForm(i,j-1); ,调用Train_into_PlatForm(4,0)得到的计算结果为14; 取i5,j0,调用算法Train_into_PlatForm(5,0)得到的计算结果为42,与理论计算公式C2nn/n+1是一致的。,3.4 队列,队列的定义 队列特殊性在于限定插入在线性表的一端进行,删除在线性表的另外一端进行。如下图所示:,先进先出(First In First Out),简称为FIFO线性表。 举例1:到医院看病,首先需要到挂号处挂号,然后,按号码顺序救诊。 举例2:乘坐公共汽车,应该在车站排队,车来后,按顺序上车。,队列的特点,初始化队列 销毁队列 入队 出队 获取队头元素内容 判断队列是否为空,队列的基本操作,顺序存储 链式存储,我们介绍顺序存储,队列的存储结构,队列的顺序存储结构,如下图所示,顺序存储的队列在内存的中的映像:,下页给出它的C语言定义:,定义队列,#define MAXSIZE 100 /*队列的最大容量*/ typedef struct DataType dataMAXSIZE; /*队列的存储空间*/ int front, rear; /*队头队尾指针*/ SeqQueue,*PSeqQueue; 定义一个指向队列的指针: SeqQueue *Q; Q=(PSeqQueue)malloc(sizeof(SeqQueue);,队列的数据区为:Q-data0Q-dataMAXSIZE -1 队头指针:Q-front(0Q-frontMAXSIZE 1) 队尾指针:Q-rear(0Q-rearMAXSIZE 1),队列的假溢出,?问会出现什么问题,一般不采用,如何解决顺序存储下队列满的问题 入队、出队有两种方式: 入队时,Q-rear加1,Q-rear=MAZXSIZE时队满 ;出队时,所有的队列元素向前移一位,Q-rear减1,Q-front始终指向0位置不变 入队时, Q-rear加1,Q-rear=MAZXSIZE时队满 ;出队时Q-fonnt加1,如何解决假溢出的问题?,队列的假溢示意图,构造循环队列,高地址,低地址,入队:Q-rear=(Q-rear+1) % MAXSIZE 出队:Q-front=(Q-front+1) % MAXSIZE,如何判断队列满和空,请看下页,判断队列满和空,牺牲一个存储空间,如本书采用队头指针指向的位置不存储元素(也可采用牺牲队尾指针指向的空间),队列空:Q-front= Q-rear 队列满:(rear+1) % MAXSIZE=front,通常采用的,接下来,给出顺序存储的循环队列操作算法:,队列基本操作算法,1、队列初始化,PSeqQueue Init_SeqQueue( ) /*返回值:新顺序队列指针,null表示失败*/ PSeqQueue Q; Q=( PSeqQueue )malloc(sizeof(SeqQueue); if (Q) Q-front=0; Q-rear=0; return Q; ,2、销毁队列 void Destroy_SeqQueue(PSeqQueue *Q) if (*Q) free(*Q); *Q=null; ,注意主程序的调用方式,main() PSeqQueue Q; Q=Init_ SeqQueue (); Destroy_ SeqQueue ( ,队列基本操作算法,3、判断队空 int Empty_SeqQueue(PSeqQueue Q) /*返回值:1表示为空,0表示非空*/ if (Q ,队列基本操作算法,4、入队 int In_SeqQueue ( PSeqQueue Q , DataType x) /*返回值:1表示成功,1表示队满溢出*/ if (Q-rear+1)%MAXSIZE=Q-front) printf(队满); return 1; /*队满不能入队*/ else Q-rear=(Q-rear+1) % MAXSIZE; Q-dataQ-rear=x; return 1; /*入队完成*/ ,队列基本操作算法,5、出队 int Out_SeqQueue (PSeqQueue Q,DataType *x) /*返回值:1表示成功,1表示队空,出队的元素保存到*x */ if (Empty_SeqQueue(Q) printf(队空); return 1; /*队空不能出队*/ else Q-front=(Q-front+1) % MAXSIZE; *x=Q-dataQ-front; return 1; /*出队完成*/ ,队列基本操作算法,6、读队头元素 int Front_SeqQueue(PSeqQueue Q ,DataType *x) /*返回值:1表示成功,1表示队空*/ if (Q-front=Q-rear) printf(队空); return 1; /*队空不能得到队头元素*/ else *x=Q-data(Q-front+1)% MAXSIZE; return 1; /*取队头元素操作完成*/ ,返回,队列基本操作算法,例1:有n个元素存储在数组An中,设计一个算法,实现将这n个元素循环左移动k(0kn)位。,思路:,利用队列,将数组的A0至Ak-1元素事先顺序放入一个队列中 然后将数组Ak至An-1元素依次左移k位 再将队列中的保存的数组元素A0至Ak-1顺序出队列,依次放入数组的An-k至An-1位置,3.5 队列的应用,循环左移动k位,void Array_LeftC

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论