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

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

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

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月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
32 1
|
26天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
49 5
|
1月前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
|
1月前
|
存储 人工智能 算法
数据结构实验之C 语言的函数数组指针结构体知识
本实验旨在复习C语言中的函数、数组、指针、结构体与共用体等核心概念,并通过具体编程任务加深理解。任务包括输出100以内所有素数、逆序排列一维数组、查找二维数组中的鞍点、利用指针输出二维数组元素,以及使用结构体和共用体处理教师与学生信息。每个任务不仅强化了基本语法的应用,还涉及到了算法逻辑的设计与优化。实验结果显示,学生能够有效掌握并运用这些知识完成指定任务。
54 4
|
1月前
|
存储 JavaScript 前端开发
执行上下文和执行栈
执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。
|
1月前
|
存储
系统调用处理程序在内核栈中保存了哪些上下文信息?
【10月更文挑战第29天】系统调用处理程序在内核栈中保存的这些上下文信息对于保证系统调用的正确执行和用户程序的正常恢复至关重要。通过准确地保存和恢复这些信息,操作系统能够实现用户模式和内核模式之间的无缝切换,为用户程序提供稳定、可靠的系统服务。
51 4
|
1月前
|
算法
数据结构之购物车系统(链表和栈)
本文介绍了基于链表和栈的购物车系统的设计与实现。该系统通过命令行界面提供商品管理、购物车查看、结算等功能,支持用户便捷地管理购物清单。核心代码定义了商品、购物车商品节点和购物车的数据结构,并实现了添加、删除商品、查看购物车内容及结算等操作。算法分析显示,系统在处理小规模购物车时表现良好,但在大规模购物车操作下可能存在性能瓶颈。
49 0
|
1月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
198 9
|
2月前
|
算法 程序员 索引
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
栈的基本概念、应用场景以及如何使用数组和单链表模拟栈,并展示了如何利用栈和中缀表达式实现一个综合计算器。
48 1
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
|
1月前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之栈和队列精题汇总(10)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第3章之IKUN和I原达人之数据结构与算法系列学习栈与队列精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!