数据结构——栈和队列
1、第三章:栈和队列
通过下面的思维导图来依次分享「栈和队列」里面重要知识点的笔记。
2、第一节:栈
1. 栈的定义:
栈(stack):只允许在一端进行插入或删除操作的线性表。
栈顶(Top):线性表允许进行插入和删除的那一端。
栈底(Bottom): 固定的,不允许进行插入和删除的另一端。
空栈:不含任何元素的空表。
栈的操作特性:后进先出(Last In First Out, LIFO),即后进入栈的元素先出栈。
2. 栈的基本操作有:
①InitStack(&S): 初始化一个空栈S。
②StackEmpty(S): 判断一个栈是否为空,若栈S为空返回true,否则返回false。
③Push(&S,x): 进栈,若栈S未满,将x加入,使之成为新栈顶。
④Pop(&S,&x): 出栈,若栈S非空,弹出栈顶元素,并用x返回。
⑤GetTop(S,&x): 读栈顶元素,若栈S非空,用x返回栈顶元素。
⑥ClearStack(&S): 销毁栈,并释放栈S占用的存储空间。
3. 栈的顺序存储结构:
①顺序栈的实现:利用一组地址连续的存储单元存放自栈底到栈顶的数据元素,同时附设一个指针(top)指示当前栈顶的位置。栈的顺序存储类型的描述为:
1#define MaxSize 50 //定义栈中元素的最大个数
2typedef struct{
3 Elemtype data[MaxSize]; //存放栈中元素
4 int top; //栈顶元素
5} SqStack;
说明:
栈顶指针:S.top,初始时设置S.top = -1,
栈顶元素:S.data[S.top];
进栈操作:栈不满时,栈顶指针先加1,再送值到栈顶元素。
出栈操作:栈非空时,先取栈顶元素值,再将栈顶指针减1。
栈空条件:S.top==-1;
栈满条件:S.top==MaxSize-1;
栈长:S.top+1。
②顺序栈的基本运算的实现:
a.栈的初始化,如下:
1void InitStack(&S){
2 s.top = -1; //初始化栈顶指针
3}
b.判断栈是否为空,如下:
1bool StackEmpty(S){
2 if(s.top==-1) //栈空
3 return true;
4 else //不空
5 return false;
6}
c.进栈,如下:
1bool Push(SqStack &S,ElemType x){
2 if(S.top==MaxSize-1) //栈满,报错
3 return false;
4 S.data[++S.top]=x; //指针先加1,再入栈
5 return true;
6}
d.出栈,如下:
1bool Pop(SqStack &S,ElemType x){
2 if(S.top==-1) //栈空,报错
3 return false;
4 x=S.data[S.top--]; //先出栈,指针再减1
5 return true;
6}
e.读栈顶元素,如下:
1bool GetTop(SqStack S,ElemType &x){
2 if(S.top==-1) //栈空,报错
3 return false;
4 x=S.data[S.top]; //x记录栈顶元素
5 return true;
6}
③共享栈:利用栈底位置相对不变的特性,可以让两个顺序栈共享一个一维数据空间,将两个栈的栈底分别设置在共享空间的两端,两个栈顶向共享空间的中间延伸,如下图:
说明:两个栈的栈顶指针都指向栈顶元素,top0=-1时0号栈为空,top1=MaxSize时1号栈为空;仅当两个栈顶指针相邻(top1-top0=1)时,判断为栈满。当0号栈进栈时top0先加1再赋值,1号栈进栈时top1先减1再赋值,出栈时刚好相反。
4. 栈的链式存储结构:采用链式存储的栈称为链栈,链栈的优点是便于多个栈共享存储空间和提高其效率,且不存在栈满上溢的情况。通常采用单链表实现,并规定所有操作都是在单链表的表头进行的,一般规则链栈没有头结点。
栈的链式存储类型可描述为,如下:
1typedef struct Linknode{
2 ElemType data; //数据域
3 struct Linknode *next; //指针域
4} *LiStack; //栈类型定义
3、第二节:队列
1. 队列的定义:
队列(Queue):队列简称队,也是一种操作受限的线性表,只允许在表的一端进行插入,而在表的另一端进行删除。向队列中插入元素称为入队或进队;删除元素称为出队或离队。
队头(Front):允许删除的一端,又称为队首。
队尾(Rear): 允许插入的一端。
空队列:不含任何元素的空表。
2. 队列的基本操作有:
①InitQueue(&Q): 初始化队列,构造一个空队列Q。
②QueueEmpty(Q): 判断一个队列是否为空,若队列Q为空返回true,否则返回false。
③EnQueue(&Q,x): 入队,若队列Q未满,将x加入,使之成为新的队尾。
④DeQueue(&Q,&x): 出队,若队列Q非空,删除队头元素,并用x返回。
⑤GetHead(Q,&x): 读队头元素,若队头Q非空,则将队头元素赋值给x.
3. 队列的顺序存储结构:
①队列的顺序实现:是指分配一块连续的存储单元存放队列中的元素,并附设两个指针front和rear分别指示队头元素和队尾元素的位置。
②队列的顺序存储类型可描述为:
1#define MaxSize 50 //定义队列中元素的最大个数
2typedef struct{
3ElemType data[MaxSize]; //存放队列元素
4int front,rear; //队头指针和队尾指针
5} SqQueues;
说明:
初始状态(队空条件):Q.front==Q.rear==0。
进队操作:队不满时,先送值到队尾元素,再将队尾指针加1。
出队操作:队不空,先取队头元素值,再将队头指针加1。
4. 队列的链式存储结构:
①队列的链式表示:称为链队列,它实际上是一个同时带有队头指针和队尾指针的单链表。
②队列的链式存储类型可描述为:
1typedef struct{ //链式队列结点
2 Elemtype data;
3 struct LinkNode *next;
4}LinkNode;
5typedef struct{ //链式队列
6 LinkNode *front,*rear; //队列的队头和队尾指针
7}LinkQueue;
说明:当Q.front==NULL且Q.rear==NULL时,链式队列为空。出队时,首先判断对是否为空,若不空,则取出队头元素,将其从链表中摘除,并让Q.front指向下一个结点(若该结点为最后一个结点,则置Q.front和Q.rear都为NULL)。入队时,建立一个新结点,将新结点插入到链表的尾部,并改让Q.rear指向这个新插入的结点(若原队列为空,则令Q.front也指向该结点)。
③链式队列的基本操作:
a.初始化,如下:
1void InitQueue(LinkQueue &Q){
2Q.front=Q.rear=(LinkNode*)malloc(sizeof(LinkNode)); //建立头结点
3Q.front->next=NULL; //初始为空
4}
b.判队空,如下:
1bool IsEmpty(LinkQueue Q){
2 if(Q.front==Q.rear) return true;
3 else return false;
4}
c.入队,如下:
1void EnQueue(LinkQueue &Q,ElemType x){
2s=(LinkNode *)malloc(sizeof(LinkNode));
3s->data=x; //创建新结点,插入到链尾
4s->next=NULL;
5Q.rear->next=s;
6Q.rear=s;
7}
d.出队,如下:
1bool DeQueue(LinkQueue &Q,Elemtype &x){
2 if(Q.front==Q.rear) return false; //空队
3 p=Q.front->next;
4 x=p->data;
5 Q.front->next=p->next;
6 if(Q.rear==p)
7 Q.rear=Q.front; //若原队列中只有一个结点,删除后变空
8 free(p);
9 return true;
10}
4、第三节:栈和队列的应用
1. 栈在括号匹配中的应用:
例如:在下面的括号序列中
括号 [ ( [ ] [ ] ) ]
括号编号1 2 3 4 5 6 7 8
①计算机接收第1个括号“[”后,期待与之匹配的第8个括号“]”出现。
②获得了第2个括号“(”,此时第1个括号“[”暂时放在一边,而急迫期待与之匹配的第7个括号“)”的出现。
③获得了第3个括号“[”, 此时第2个括号“(”暂时放在一边,而急迫期待与之匹配的第4个括号“]”的出现。第3个括号的期待得到满足,消解之后,第2个括号的期待匹配又成为当前最急迫的任务了。
④依此类推,可见,该处理过程与栈的思想吻合。
算法的思想如下:
①初始设置一个空栈,顺序读入括号。
②若是右括号,则或者使置于栈顶的最急迫期待得以消解,或者是不合法的情况(括号序列不匹配,退出程序)。
③若是左括号,则作为一个新的更急迫的期待压入栈中,自然使原有的在栈中的所有未消解的期待的急迫性降了一级。算法结束时,栈为空,否则括号序列不匹配。
2. 队列在计算机系统中应用:
①解决主机与外部设备之间速度不匹配的问题 :
以主机和打印机之间速变不匹配的问题为例,主机输出数据给打印机打印,输出数据的速度比打印数据的速度要快得多,由于速度不匹配,若直接把输出的数据送给打印机打印显然是不行的。
解决的方法是:设置一个打印数据缓冲区, 主机把要打印输出的数据依次写入到这个缓冲区中,写满后就暂停输出,转去做其他的事情。打印机就从缓冲区中按照先进先出的原则依次取出数据并打印,打印完后再向主机发出请求。主机接到请求后再向缓冲区写入打印数据。这样做既保证了打印数据的确,又使主机提高了效率。故打印数据缓冲区中所存储的数据就是一个队列。
②解决由多用户引起的资源竞争问题 :
CPU(即中央处理器,它包括运算器和控制器)资源的竞争为例,在个带有多终端的计算机系统上,有多个用户需要CPU各自运行自己的程序,它们分别通过各自的终端向最操作系统提出占用CPU的请求。操作系统通常按照每个请求在时间上的先后顺序,把它们排成个队列,每次把CPU分配给队首请求的用户使用。当相应的程序运行结束或用完规定的时间间隔后,则令其出队,在把CPU分配给新的队首请求的用户使用。这样既满足了每个用户的请求,又使CPU能够正常运行。
将自己的学习笔记通过本博客展现出来,也是对自己学习的一种记录。
扫一扫,关注公众号