【数据结构】第三章 栈、队列和数组

简介: 栈和队列指的是只允许在一段进行操作的线性表

本章通常以选择题的形式考查,题目不算难,但命题的形式比较灵活,其中栈(出入栈的过程、出栈序列的合法性)和队列的操作及其特征是重点。由于它们均是线性表的应用和推广,因此也容易出现在算法设计题中。此外,栈和队列的顺序存储、链式存储及其特点、双端队列的特点、栈和队列的常见应用,以及数组和特殊矩阵的压缩存储都是读者必须掌握的内容。

1 栈

栈指的是只允许在一段进行操作的线性表,如图所示

  1. 栈顶:线性表允许插入删除的一端,比如有某个栈S=(a1,a2,a3,a4,a5),则进栈顺序是a1->a5,出栈顺序是a5->a1(先进后出)
  2. 栈底:固定端,不允许插入删除

空栈即不包含任何元素

1.1 栈的顺序存储实现(顺序栈)

采用顺序存储的栈称为顺序栈,它利用一组地址连续的存储单元存放自栈底到栈顶的数据元素,同时设一个指针 (top)指示当前栈顶元素的位置。

#define MaxSize 50  // 定义栈中元素的最大个数
typedef strudct{
        ElemType data[MaxSize];  // 存放栈元素
    int top;  // 栈顶指针
}SqStack;
  1. 栈顶元素S.top,初始设置为-1
  2. 栈顶元素S.data[S.top]

1.2 顺序栈的基本操作

Initstack(&S):初始化一个空栈S。

stackEmpty(S):判断一个栈是否为空,若栈S为空则返回true,否则返回false。

Push(&S,x):进栈,若栈 S 未满,则将加入使之成为新栈顶。

Pop(&S,&x):出栈,若栈S 非空,则弹出栈顶元素,并用x返回。

GetTop(S,&x):读栈顶元素,若栈S 非空,则用x返回栈顶元素。

Destroystack(&S):销毁栈,并释放 S 占用的存储空间 (“&”表示引用调用)。

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

1.2.1 初始化栈

void Initstack(&S){
    S.top = -1;
}

1.2.2 判栈空/栈满

栈空条件:S.top==-1

bool stackEmpty(&S){
    if(S.top == -1) return true;
    else return false
}

栈满条件:S.top==MaxSize-1

bool stack(&S){
    if(S.top==MaxSize-1) return true;
    else return false;
}

1.2.3 进栈

void Push(&S, x){
    if(S.top==MaxSize-1) return false;  // 判断栈满
    S.data[++S.top] = x;
    return true;
}

1.2.4 出栈

void Pop(&S){
    if(S.top == -1) return false;  // 判断栈空
    S.top -= 1;
    return true;
}

1.2.5 读取栈顶元素

void GetTop(&S){
    if(S.top == -1) return false;
    x = S.data[S.top];
    return true;
}

1.3 共享栈

利用栈底位置相对不变的特性,让两个顺序栈共享一个一维数组空间,将两个栈的栈底分别设置在共享空间的两端,两个栈顶向共享空间的中间延伸,如图3.3所示。

  1. 两侧都设置栈顶指针,当top0==-1时栈0为空 ,当top1==MaxSize时栈1为空
  2. 当top0-top1=0时栈满

1.4 栈的链式结构实现——链栈

通常采用单链表实现,并规定所有操作都是在单链表的表头进行的。

规定链栈没有头结点,Lhead直接指向栈顶元素。

链式存储结构类型:

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

1.5 顺序栈和链栈的对比

顺序栈的入栈操作受数组上界的约束,当对栈的最大使用空间估计不足时,有可能发生栈上溢,此时应及时向用户报告消息,以便及时处理,避免出错。

链栈的优点是便于多个栈共享存储空间和提高其效率,且不存在栈满上溢的情况。

1.6 栈在括号匹配中的应用

1.7 栈在表达式求值中的应用

中缀表达式A+B*(C-D)-E/F 所对应的后缀表达式为ABCD-*+EF/-(中缀表达式如何变成后缀表达式?)

通过后缀表示计算表达式值的过程为:

顺序扫描表达式的每一项,然后根据它的类型做如下相应操作:

  1. 若该项是操作数,则将其压入栈中;
  2. 若该项是操作符<op>则连续从栈中退出两个操作数 Y和,形成运算指令X<op>Y,并将计算结果重新压入栈中。

例如上图的计算过程应该如下:

1.8 栈在递归中的应用(P90)

2 队列

队列也是一种受限的线性表,其要求在一段进行插入,另一端进行删除。

  1. 队头:允许删除的一端
  2. 队尾:允许插入的一端
  3. 特性:先进先出

2.1 队列的顺序存储结构

顺序存储结构描述

#define MaxSize 50  // 定义队列中元素的最大数
typedef struct{
        ElemType data[MaxSize];  // 存放队列指针
        int front, rear;  // 队头指针和队尾指针
}SeQueue;

  1. front==rear==0为空队列,如图(a)。但是rear==MaxSize不能判断为满队列,如图(d),这种情况叫做假溢出

2.2 循环队列

为了解决假溢出的问题提出循环队列。将顺序队列臆造为一个环状的空间即把存储队列元素的表从逻辑上视为一个环,称为循环队列。

队满和队空的处理方案有三个:

2.2.1 方案一

牺牲一个单元来区分队空和队满,如图(d)

  1. 队满条件:(Q.rear+1)%MaxSize==Q.front
  2. 队空条件:Q.front==Q.rear
  3. 队列中元素个数:(Q.rear-Q.front+MaxSize)%MaxSize

2.2.2 方案二

增设元素个数的的数据单元,两种情况都存在Q.front==Q.rear

  1. 队空条件:Q.size==0
  2. 队满条件:Q.size==MaxSize

2.2.3 方案三

类型中增设 tag 数据成员,以区分是队满还是队空。tag 等于 0时,若因删除导致Q.front==Q.rear,则为队空;tag等于1时,若因插入导致Q.front==Q.rear,则为队满。

2.3 队列的链式存储结构

实际上是一个同时带有队头指针和队尾指针的单链表。头指针指向队头结点,尾指针指向队尾结点,即单链表的最后一个结点(注意与顺序存储的不同)。

2.3.1 不带头结点的链式队列

存储类型可描述为:

typedef struct LinkNode{
    ElemType data;
    struct LinkNode *next;
}LinkNode;
typedef struct{
    LinkNode *front, *next;
}*LinkQueue;
  1. 当font==NULL==read==NULL时为空链式队列

2.3.2 带头结点的链式队列

不带头结点的链式队列在两端操作会与中间操作不同,为了统一操作,一般使用带有头结点的链式队列

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

初始化

void InitQueue(LinkQueue &Q){
    Q.front=Q.rear=(LinkNode)*malloc(sizeof(LinkNode));  // 建立头结点
    Q.front -> next = NULL;
}

判队空

bool isEmpty(LinkQueue*Q, ElemType x){
    if(Q.front==Q.rear) return true;
    return false;
}

入队

void EnQueue(LinkQueue &Q,ElemType x){
    LinkNode*s=(LinkNode *)malloc(sizeof(LinkNode));
    //创建新结点,插入到链尾
    S->data=x;s->next=NULL;
    Q.rear->next=s;
    Q.rear=s;
}

出队

bool DeQueue(LinkQueue &Q, ElemType &x){
    if(Q.front==Q.rear) return false;
    LinkNode *p = Q.front->next;
    x=p->data;
    Q.front->next=p->next;
    if(Q.rear==p):
        Q.rear=Q.front;
    free(q);
    return true;
}

2.4 队列在层次遍历中的应用

在信息处理中有一大类问题需要逐层或逐行处理。这类问题的解决方法往往是在处理当前层或当前行时就对下一层或下一行做预处理,把处理顺序安排好,等到当前层或当前行处理完毕,就可以处理下一层(比如二叉树的遍历

过程如下:

  1. 根节点入队
  2. 若队空,处理完毕。否则,执行步骤3

队列中第一个结点出队,并访问之。若其有左孩子,则将左孩子入队。若其有右孩子,则将右孩子入队,返回步骤2。

4 数组

在数据结构中考虑的是如何用最小的内存空间来存储同样的一组数据。所以,我们不研究矩阵及其运算等,而把精力放在如何将矩阵更有效地存储在内存中,并能方便地提取矩阵中的元素。

数组是由n(n>1)个相同类型的数据元素构成的有限序列,每个数据元素称为一个数组元素,每个元素在 n 个线性关系中的序号称为该元素的下标,下标的取值范围称为数组的维界。

3.1 一维数组的存储结构

一个数组所有元素在内存中占一段连续的存储空间。

存储结构关系为 $$Loc(a_i)=Loc(a_0)+i*L$$,其中L代表每个元素占有的单元格

3.2 多为数组

以二维数组为例,分为行优先和列优先。

3.2.1 行优先

假设行下标范围为$$[0, h_1]$$,列下标为$$[0, h_2]$$,则存储关系为:

$$Loc(a_{ij})=Loc(a_{00})+[i*(h_2+1)+j]*L$$

3.2.2 列优先

假设行下标范围为$$[0, h1]$$,列下标为$$[0, h2]$$,则存储关系为:

$$Loc(a_{ij})=Loc(a_{00})+[j*(h_1+1)+i]*L$$

5 特殊矩阵的压缩存储

压缩存储,指为多个值相同的元素只分配一个存储空间,对零元素不分配存储空间(找出特殊矩阵中值相同的矩元素的分布规律,把那些呈现规律性分布的、值相同的多个矩阵元素压缩存储到一个存储空间中)

特殊矩阵指的是具有许多相同矩阵元素或零元素,并且这些相同矩阵元素或零元素的分布有一

定规律性的矩阵(如对称矩阵、上(下)三角矩阵、对角矩阵等)

5.1 对称矩阵

若对一个n阶矩阵4中的任意一个元素a都有$$a_{ij}=a_{ji}$$,则称其为对称矩阵。

此时将二维矩阵用一维矩阵$$B[n*(n+1)/2)]$$来表示(只保存下三角区或上三角区及对角线元素,节省空间),即$$b_k=a_{ij}$$

元素下标k和ij之间的关系如下:

5.2 三角矩阵

下三角矩阵图(a)中,上三角元素均为同一常量。可采用与对称矩阵相同的储存思想,将二维矩阵用一维矩阵$$B[n*(n+1)/2 +1]$$来表示

存储结构图如下:

元素下标关系如下:

5.3 三对角矩阵/带状矩阵

如下图所示,$$|i-j|>0$$元素为0

  1. 三对角矩阵对角线元素下标对应关系为$$k=2*i+j-3$$
  2. 由k求i和j公式如下$$i=[(k+1)/3+1]; j=k-2*i+3$$

6 稀疏矩阵

矩阵中非零元素的个数t,相对矩阵元素的个数。来说非常少,即$$s>>t$$的阵称为稀疏矩阵。

由于稀疏矩阵非零元素很少,因此采用存储非零元素来存储。但是,这里存在一个问题,即非零元素为非规律性存储,因此便采用三元组的方式同时存储坐标信息

三元组可以采用数组或十字链表法存储。

相关文章
|
1月前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
142 77
|
3天前
|
DataX
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。
|
15天前
|
存储 人工智能 算法
C 408—《数据结构》算法题基础篇—数组(通俗易懂)
408考研——《数据结构》算法题基础篇之数组。(408算法题的入门)
58 23
|
1月前
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
43 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
1月前
|
存储 C语言 C++
【C++数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现链栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储整数,最大
46 9
|
1月前
|
C++
【C++数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】
【数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】(1)遇到左括号:进栈Push()(2)遇到右括号:若栈顶元素为左括号,则出栈Pop();否则返回false。(3)当遍历表达式结束,且栈为空时,则返回true,否则返回false。本关任务:编写一个程序利用栈判断左、右圆括号是否配对。为了完成本关任务,你需要掌握:栈对括号的处理。(1)遇到左括号:进栈Push()开始你的任务吧,祝你成功!测试输入:(()))
38 7
|
3月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
332 9
|
3月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
53 1
|
3月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
99 5
|
3月前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
116 21