数据结构——栈和队列

概述

线性表分为一般线性表操作受限的线性表线性表的推广


1、栈

栈的基本概念

只允许在一端进行插入或删除操作的线性表称为

栈的一个操作特性是后进先出(LIFO),故栈又被称为后进先出的线性表

每接触到一种新的数据结构类型,都应该从数据结构的三个要素着手,以加深对定义的理解

相关名字:栈顶栈底空栈

栈的基本操作:

  • InitStack(&S)
  • StackEmpty(S)
  • Push(&S, x):进栈
  • Pop(&S, &x):出栈
  • GetTop(S, &x):读栈顶元素
  • ClearStack(&S):销毁栈

在解答算法题时,若题干没有做出限制,可以直接使用这些基本的操作函数


栈的顺序存储结构

栈的顺序存储结构称为顺序栈

#define MaxSize 50
typedef struct{
    ElemType data[MaxSize];
    int top;
}SqStack;

对于栈和队列的判空和判满条件会因为实际给的条件不同而变化(要灵活使用)

一般来说栈顶指针指向的就是栈顶元素

共享栈:利用栈底位置相对不变的特性,可以让两个顺序栈共享一个一维数据空间,将两个栈的栈底分别设置在共享空间的两端,两个栈顶向共享空间的中间延伸

共享栈涉及到的一些基本操作要理解(灵活应用)

共享栈是为了更有效地利用存储空间,两个栈的空间相互调节,只有在整个存储空间被沾满时才发生上溢。其存取数据的时间复杂度均为 O ( 1 ) ,所以对存取效率没有什么影响


栈的链式存储结构

采用链式存储的栈称为链栈,链栈的优点是便于多个栈共享存储空间(应该要在结点中设置一个计数器)提高其效率,且不存在栈满上溢的情况。

由于栈的插入和删除都是在栈顶进行的,顺序栈和链式栈对于这两种操作并没有明显的性能区别,节省存储空间是可以体现出来的?????

通常采用单链表实现,并规定所有操作都是在单链表的表头进行的。这里规定链栈没有头结点 L h e a d 指向栈顶元素

typedef struct Linknode{
    ElemType data;
    struct Linknode *next;
}Linknode, *LiStack;

对于带头结点和不带头结点的链栈,在具体的实现方面有所不同

n个不同元素进栈,出栈序列的种数为 1 n + 1 C 2 n n ,该公式叫做卡特兰数


栈相关的算法设计题

  • 3、判断一个入出栈操作序列的合法性
  • 4、判断链栈中保存的字符串是否中心对称
  • 5、设计共享栈的入栈和出栈的操作算法(答案设计的那种结构没有意义,因为两个栈存储了相同的数据)

2、队列

队列的基本概念

队列是一种操作受限的线性表,只允许在表的一端进行插入,而在表的另一端进行删除

队列的操作特性是先进先出(FIFO),故又称为先进先出的线性表

相关名词:入队(进队)、出队(离队)、队头(队首)、队尾、空队列

单词queen

队列的基本操作:

  • InitQueue(&Q)
  • QueueEmpty(Q)
  • EnQueue(&Q, x)
  • DeQueue(&Q, &x)
  • GetHead(Q, &x)

队列的顺序存储结构

队列的顺序实现是指分配一块连续的存储单元存放队列中的元素,并附设两个指针frontrear分别指示队头元素和队尾元素的位置。设队头指针指向队头元素队尾指针指向队尾元素的下一个位置(也有其他设置方法)

#define MaxSize 50
typedef struct{
    ElemType data[MaxSize];
    int front, rear;
}SqQueue;

由于顺序队列存在假溢出的缺陷,所以我们引入了循环队列(其实还是顺序队列,只是通过取余运算实现了元素指针的循环)

初始状态:Q.front = Q.rear = 0
出队指针变化:Q.front = (Q.front + 1) % MaxSize
入队指针变化:Q.rear = (Q.rear + 1) % MaxSize

通过牺牲一个单元来区分队空和队满(以队头指针在队尾指针的下一个位置作为队满的标志),这是一种常用的实现方法
队空条件:Q.front == Q.rear
队满条件:(Q.rear + 1) % MaxSize == Q.front
有效元素个数:(Q.rear - Q.front + MaxSize) % MaxSize


队列的链式存储结构

队列的链式表示称为链队列,它实际上是一个同时带有队头指针队尾指针的单链表。头指针指向队头结点,尾指针指向队尾结点

typedef struct LinkNode{
    ElemType data;
    struct LinkNode *next;
}LinkNode;

typedef struct{
    LinkNode *front, *rear;
}LinkQueue;

不设头结点的链式队列在插入和删除操作上不具有统一性,为了具有统一性,常常为链式队列设置头结点

用单链表表示的链式队列特别适合于数据元素变动较大的情形,而且不存在队列满且产生溢出的问题

如果在程序中要使用多个队列,与多个栈的情形一样,最好使用链式队列,这样就不会出现存储分配不合理溢出的问题

个人觉得栈和队列无论是顺序实现还是链式实现,性能都不是关键了(因为操作主要集中在队列两端和栈顶,不需要移动大量元素),关键点是溢出问题


双端队列

双端队列是指允许两端都可以进行入队和出队操作的队列,其两端分别称为前端和后端

怎么分前端和后端?????

在双端队列出队时:无论前端还是后端出队,先出的元素排列在后出的元素的前面

输出受限的双端队列

输入受限的双端队列

如果限定双端队列从某个端点插入的元素只能从该端点删除,则该双端队列就蜕变为两个栈底相邻接的栈了

个人觉得双端队列的那个例题没有意义!!!!!!!!

关于从不同的端点出队和入队操作的记法,不是很理解,并不是单词首字母,个人觉得很恶心


队列相关的算法设计题

  • 1、通过标志域 t a g 来区分队空和队满(当Q.front == Q.rear)的循环队列的入队和出队算法的实现
  • 2、利用一个空栈作为辅助空间(默认足够大)来逆置一个队列(可直接使用基本操作函数)
  • 3、利用两个栈来模拟一个队列,实现入队、出队和判空操作(可直接使用栈的基本操作函数)

基本操作函数已屏蔽数据结构所采用的存储结构

个人感觉双端队列的习题没什么意义


3、栈和队列的应用

栈在括号匹配中的应用

括号匹配的处理过程与栈的思想吻合


栈在表达式求值中的应用

表达式求值是程序设计语言编译中一个最基本的问题,它的实现是栈应用的一个典型范例

中缀表达式不仅依赖运算符的优先级,而且还要处理括号

后缀表达式的运算符在操作数后面,在后缀表达式中已考虑了运算符的优先级,没有括号,只有操作数和运算符

中缀表达式转化为后缀表达式的过程

表达式树

用栈来计算用后缀表达式表示的表达式的值,栈顶存放的是最后的计算结果


栈在递归中的应用

递归是一种重要的程序设计方法

如果在一个函数、过程或数据结构的定义中又应用了它自身,那么这个函数、过程或数据结构称为是递归定义的,简称递归

递归通常把一个大型的复杂问题,层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的代码就可以描述出解题过程所需要的多次重复计算,这大大地减少了程序的代码量。但在通常情况下,它的效率并不是太高

递归模型不能是循环定义的,其必须包含递归表达式(递归体)和边界条件(递归出口)

递归的精髓在于能否将原始问题转换为属性相同但规模较小的问题

在递归调用的过程中,系统为每一层的返回点、局部变量、传入实参等开辟了递归工作栈来进行数据存储。递归次数过多容易造成栈溢出等

递归效率不高的原因是其调用过程中包含很多重复的计算

递归的效率低下,但优点是代码简单,容易理解

《编译原理》详细介绍了递归的具体实现

可以将递归算法转换为非递归算法,通常需要借助栈来实现这种转换(不理解转换这个词的含义?????)。即通常来说递归算法的非递归实现需要用到栈


队列在层次遍历中的应用

层次遍历的解决方法往往是在处理当前层或当前行时就对下一层或下一行做预处理,把处理顺序安排好,待当前层或当前行处理完毕,就可以处理下一层或下一行。使用队列是为了保存下一步的处理顺序


队列在计算机系统中的应用

解决主机与外部设备之间速度不匹配的问题

解决由多用户引起的资源竞争问题


栈和队列的应用相关的算法设计题

图的广度优先搜索需要用到队列

中缀表达式转化为后缀表达式的转换思想要掌握(这里涉及到一个运算符优先级表格,同一运算符栈内和栈外的优先级是不一样的)。具体去运行一次

有一种将中缀表达式转化为后缀或前缀表达式的快捷方法

  • 1、括号匹配算法的实现
  • 2、火车车厢调度算法的实现
  • 3、递归函数的非递归实现
  • 4、渡口管理算法的实现(一定要彻底理解题目意思)

4、特殊矩阵的压缩存储

猜你喜欢

转载自blog.csdn.net/weixin_39918693/article/details/81067994