数据结构论坛

首页 » 分类 » 问答 » 计算机数据结构考研复习栈和队列
TUhjnbcbe - 2024/9/17 14:57:00
白癜风病可以治愈 https://baike.baidu.com/item/%E9%A3%8E%E6%9D%A5%E4%BA%86%C2%B7%E5%B8%A6%E4%BD%A0%E8%B5%B0%E5%87%BA%E7%99%BD%E7%99%9C%E9%A3%8E%E9%98%B4%E9%9C%BE/20783753?fr=aladdin

第三章栈和队列一、大纲要求二、知识点梳理三、实际应用:

第三章栈和队列一、大纲要求

栈和队列的基本概念

栈和队列的顺序存储结构

栈和队列的链式存储结构

栈和队列的应用

特殊矩阵的压缩存储

二、知识点梳理

栈定义:

一类受限的线性表,只允许在一端进行插入和删除操作。

栈顶:

可以运行插入和删除的一端

栈底:

与栈顶相对

上图操作受限的线性表

本质上而言栈和队列都是线性表

特点:

FILO先进后出firstinlastout可以想成是开车进一个死胡同只能从后倒车

存储结构:

顺序栈和链栈(原因在与栈的本质是线性表)

数学性质:

N=(1/n+1)C(n:2n)

//顺序栈的定义Ttypeofstruct{Tdata[maxsize];ttop;//栈顶指针}SqStack;//顺序栈类型定义//考研中基本不需要自己定义算法题会提前告知xxstackxxstackvoidfunc(xxstack,,,){函数体}//链栈节点定义typeofstructLNode{intdata;structLNode*next;//本体引用//考链栈的题目几乎为0}LNode;

//栈

队列的定义:

也是一种操作受限的线性表,限制其在表的一端进行插入而在另一端进行删除操作。

队尾:

可以插入的一端

队头:

可以删除的一段

特点:

FIFO可以想象成是开车过隧道,隧道一端进另一端出。

存储结构:

顺序队和链队

//顺序队列的定义typeofstruct{intdata[maxsize];intfront;intrear;}SqQueue;//链队节点定义typeofstructQNode{intdata;structQNode*next;}QNode;//链队类型定义typeofstruct{QNode*front;QNode*rear;}LiQueue;

顺序栈:

两个状态和两个操作

空栈:

top=-1

满栈:

top=maxSize-1

非法状态:

上溢和下溢(上溢即栈满仍进,下溢即空栈仍出)

进栈:

++top;data[top]=x;

//或data[++top]=x;

出栈:

x=data[top];top--;

//或x=data[top--];

//初始化栈stvoidinitStack(SqStackst){st.top=-1;//***}//判断栈空boolisEmpty(SqStackst){if(st.top==-1)returntrue;elsereturnfalse;}//进栈intpush(SqStackst,intx){if(st.top==maxSize-1)//栈满判断语句return0;else:st.data[++st.top]=x;//***return1;}//出栈intpop(SqStackst,intx){if(st.top==-1)//判断栈空return0;else:x=st.data[st.top--]//***return1;}

链栈:

使用比顺序栈少但是相似lst

栈空:

lst-next=NULL;

栈满:

存在吗?内存上线?

进栈:

建立元素p在链lst的头插lst是头结点

p-next=lst-next;lst-next=p;

出栈:

单链表的删除操作

p=lst-next;x=p-data;lst-next=p-next;free(p);

//链栈的初始化操作voidinitStack(Lnode*lst){lst=(LNode*)malloc(sizeof(LNode));lst-next=NULL;}//判断栈空代码boolisEmpty(LNode*lst){if(lst-next==NULL)returntrue;elsereturnfalse;}//进栈代码voidpush(LNode*lst,intx){LNode*p;p=(LNode*)malloc(sizeof(LNode));p-next=NULL;//头插p-data=x;p-next=lst-next;lst-next=p;}//出栈代码intpop(LNode*lst,intx){LNode*p;if(lst-next==NULL)return0p=lst-next;//指向栈顶x=p-data;lst-next=p-next;//删除free(p);//释放内存return1;}

总结:

以上可以看出顺序栈的使用是比链栈简单很多的,因此在考试的频率上特别是算法题,基本都是顺序栈,不用链栈。

顺序队

如图所示,不可以继续添加元素,否则会造成数组越界而遭致程序出错。然而此时又不应该扩充数组,因为还有大量实际空间未被占用。此时我们应该如何解决这个问题呢?我们将其实现为循环队列。

循环队列(解决假溢出问题)

两个状态:

队空:qu.rear==qu.front

队满:(qu.rear+1)%maxSize==qu.front

两个操作:

进队:

qu.rear=(qu.rear+1)%maxSize;

qu.data[qu.rear]=x

出队:

qu.front=(qu.front+1)%maxSize;

x=qu.data[qu.front]

(这种设计有什么好处呢)

思考:

当rear==front的时候,队列可能是满,也可能是空。

因为存在满和空两种情况,我们需要分别判断:

☆满:当队列添加元素到rear的下一个元素是front的时候,也就是转圈子要碰头了,我们就认为队列满了。(Q.rear+1)%MAXSIZE=Q.front

☆空:当队列删除元素到frontrear的时候,我们认为队列空了。**Q.rearQ.front不一定为0**


  图示:


  
  

 说明:

为了解决刚才这个问题令队列空间中的一个单元闲置,使得队列非空时,Q.rear与Q.front之间至少间隔一个空闲单。

队列长度:Q.rear-Q.front;

队头元素:Q.base[Q.front+1];

队尾元素:Q.base[Q.rear];

//初始化队列voidinitQueue(SqQueuequ){qu.front=qu.rear=0;}//判断队空intisQueueEmpty(SqQueuequ){if(qu.front==qu.rear)return1;elsereturn0;}//进队算法intenQueue(SqQueuequ,intx){if((qu.rear+1)%maxSize==qu.front)//判断队满return0;qu.rear=(qu.rear+1)%maxSize;qu.data[qu.rear]=x;return1;}//出队算法intdeQueue(SqQueuequ,intx){if(qu.front==qu.rear)//判断是否队空return0;qu.front=(qu.front+1)%maxSize;x=qu.data[qu.front];return1;}

链队:

队空:lqu-rear==NULL;/lqu-front==NULL;

队满:存在吗(内存上限)

进队:(相当于非空链尾插)

lqu-rear-next=p;

lqu-rear=p;

出队:(相当于多元素链(1)头删除)

p=lqu-front;

lqu-front=p-next;

x=p-data;free(p);

//初始化voidinitQueue(LiQueue*lqu){lqu=(Liqueue*)malloc(sizeof(LiQueue))lqu-front=lqu-rear==NULL;}//判断队空intisQueueEmpty(LiQueue*lqu){if(lqu-rear==NULL

lqu-front==NULL)return1;elsereturn0;}//入队voidenQueue(LiQueue*lqu,intx){QNode*p;p=(QNode*)malloc(sizeof(QNode));p-data=x;p-next=NULL;if(lqu-rear==NULL)//空队加第一个元素lqu-front=lqu-rear=p;else{//尾插法lqu-rear-next=p;lqu-rear=p;}}//出队代码intdeQueue(LiQueue*lqu,intx){QNode*p;if(lqu-rear==NULL)return0;elsep=lqu-front;if(lqu-front==lqu-rear)//单元素链的出队处理lqu-front=lqu-rear=NULL;else//删除一个节点lqu-front=lqu-front-next;x=p-data;free(p);return1;//出队成功}

总结:能用顺序队就用顺序队。

三、实际应用:

1.动态扩容顺序栈

2.栈在函数中的调用(寄存器)

栈是程序设计中的一种经典数据结构,每个程序都拥有自己的程序栈。栈帧也叫过程活动记录,是编译器用来实现函数调用过程的一种数据结构。C语言中,每个栈帧对应着一个未运行完的函数。从逻辑上讲,栈帧就是一个函数执行的环境:函数调用框架、函数参数、函数的局部变量、函数执行完后返回到哪里等等。栈是从高地址向低地址延伸的。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。

寄存器ebp(basepointer)指向当前的栈帧的底部(高地址),可称为“帧指针”或“基址指针”;寄存器esp(stackpointer)指向当前的栈帧的顶部(低地址),可称为“栈指针”。

在C和C++语言中,临时变量分配在栈中,临时变量拥有函数级的生命周期,即“在当前函数中有效,在函数外无效”。这种现象就是函数调用过程中的参数压栈,堆栈平衡所带来的。堆栈平衡是指函数调完成后,要返还所有使用过的栈空间。

函数调用其实可以看做4个过程:

压栈:函数参数压栈,返回地址压栈

跳转:跳转到函数所在代码处执行

执行:执行函数代码

返回:平衡堆栈,找出之前的返回地址,跳转回之前的调用点之后,完成函数调用

3.栈进行表达式求值

首先提出中前后缀表达式相互转换问题

//只要遇到中转前的必须复习一下这个

波兰表示法(中缀转前缀)

首先设定一个操作符栈,从右到左顺序扫描整个中缀表达式,如果是操作数,则直接归入前缀表达式;

如果是操作符,则检测器是否是右括号,如果是右括号,则直接将其入栈;

如果是左括号,则将栈中的操作符依次弹栈,归入前缀表达式,直至遇到右括号,将右括号弹栈,处理结束;

如果是其他操作符,则检测栈顶操作符的优先级与当前操作符的优先级关系,

如果栈顶操作符优先级大于当前操作符的优先级,则弹栈,并归入前缀表达式,直至栈顶操作符优先级小于等于当前操作符优先级,这时将当前操作符压栈。

当扫描完毕整个中缀表达式后,检测操作符栈是否为空,如果不为空,则依次将栈中操作符弹栈,归入前缀表达式。

//总结:1.中转前用了1个栈:操作符栈(也可以是两个操作数栈/表达式栈,不用逆序输出)

2.中转前怎么遍历:从右到左

3.中转前遇到右括号:入栈,而且如果非括号操作符遇到栈顶是右括号的,直接入栈。

4.无括号表达式

逆波兰表示法(中缀表达式转换成后缀表达式算法)

从左至右扫描一中缀表达式;

若读取的是操作数,则判断该操作数的类型,并将该操作数存入操作数堆栈;

若读取的是运算符:

(1)若运算符堆栈栈顶的运算符为括号,则直接存入运算符堆栈。

(2)若比运算符堆栈栈顶的运算符优先级高或相等,则直接存入运算符堆栈。

(3)若比运算符堆栈栈顶的运算符优先级低,则输出栈顶运算符到操作数堆栈,并将当前运算符压入运算符堆栈

该运算符为左括号"(",则直接存入运算符堆栈;

该运算符为右括号")",则输出运算符堆栈中的运算符到操作数堆栈,直到遇到左括号为止;

该运算符为非括号运算符:

当表达式读取完成后运算符堆栈中尚有运算符时,则依序取出运算符到操作数堆栈,直到运算符堆栈为空。

总结:1.中转后用了2个栈:操作数栈(逆序输出)操作符栈

2.中转后怎么遍历:从左到右

3.中转后遇到左括号:入栈

4.无括号表达式

求解波兰表达

从运算数侧依次扫描。

如果扫描的项目是操作数,则将其压入操作数堆栈,并扫描下一个项目。

如果扫描的项目是一个运算符,则对栈的顶上两个操作数执行该运算。

将运算结果重新压入堆栈。

重复步骤第2-5步,堆栈中即为结果值。

4.进制转换

以二进制为例余数依次进制最后全部出栈

5.栈进行括号匹配(编辑器)

//栈//左括号入栈进栈//出栈匹配if}or】or):top{[(出栈else:0//循环结束ifstackisnull=1:return1else:return0

6.实现浏览器页面进退访问

7.共享栈

结构体

7.双端队列

1
查看完整版本: 计算机数据结构考研复习栈和队列