深入浅出带你玩转栈与队列——【数据结构】

简介: 深入浅出带你玩转栈与队列——【数据结构】

在前几期的学习中,我们学习了顺序表与链表,今天我们将学习一种新的数据结构——栈与队列。而栈与队列实际上就是链表的一种变形产物,但肯定会有许多结构上的不同。


现在就让我们进入栈和队列,看看它们的特点!!!

1.栈

1.1栈的概念及结构

栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。


压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶


出栈:栈的删除操作叫做出栈。出数据也在栈顶。

1.2栈的结构特征图

文字力量有时太过渺小,我们通过图解更清楚的了解栈的特点。

e0ee692eb70145d888c85f11e60d0787.png


5b45d44feed04ecf979b7884e92a0ac3.png

1.3栈的实现

栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。

1.3.1栈的初始化

void STInit(ST* ps)
{
  assert(ps);
  ps->a = NULL;
  ps->capacity = 0;
  ps->top = 0;
}

这里我们使用数组作为结构,更好的适用于栈的特征。但是这又与顺序表中的不同,顺序表在刚进入就进行了开辟空间,而我们栈的初始化先全部设为0。

1.3.2进栈

在进栈时,我们会遇到栈内存满的情况,所以我们必须进行判断(top 有效内容与 capacity容量是否相等),如果相等我们就进行realloc进行扩容即可。然后将需要放入栈的内容存入堆区,有效内容top++即可完成。如果内容开始为0,我们使用三元表达式直接扩容为4。

void STPush(ST* ps, STDataType x)
{
  assert(ps);
  if (ps->top == ps->capacity)
  {
    int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
    STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * newCapacity);
    if (tmp == NULL)
    {
      perror("realloc");
      exit(-1);
    }
    ps->a[ps->top] = x;
    ps->top++;
  }
}


注意:malloc与realloc都可以使用,当给予realloc一个指针时,功能完全相同

1.3.3出栈

出栈非常简单,只需要将top--,不去访问其内容即可。

void STPop(ST* ps)
{
  assert(ps);
  assert(ps->top > 0);
  --ps->top;
}

1.3.4销毁内存

销毁堆上内存也非常简单,只需free掉即可

void STDestroy(ST* ps)
{
  assert(ps);
  free(ps->a);
  ps->a = NULL;
  ps->top = ps->capacity = 0;
}


但是我们一定要用assert进行断言,防止传入空指针。

1.3.5判断栈是否为空

栈中是否插入内容,我们可以创建一个函数进行判断一下。返回值为bool类型,我们需要在头文件中加入#include<stdbool.h>才可以使用。

bool STEmpty(ST* ps)
{
  assert(ps);
  return ps->top == 0;
}

1.3.5栈底元素的读取

在读取栈底元素时,我们需要检查传入栈的指针是否正确以及栈中是否为空,两个必须都为真即可进行下面操作。

STDataType STTop(ST* ps)
{
  assert(ps);
  assert(ps->top > 0);
  return ps->a[ps->top - 1];
}

栈底元素就是top-1的内容,我们直接返回即可。

1.3.6栈中大小

当我们一直添加数据,需要了解栈中有多少个有效数据时,我们却束手无策,所以我们还需要一个能测出栈中大小函数。

int STSize(ST* ps)
{
  assert(ps);
  return ps->top;
}

1.4栈实现所有接口

我们需要了解的所有接口,可以放在头文件中:

// 下面是定长的静态栈的结构,实际中一般不实用,所以我们主要实现下面的支持动态增长的栈
typedef int STDataType;
#define N 10
typedef struct Stack
{
 STDataType _a[N];
 int _top; // 栈顶
}Stack;
// 支持动态增长的栈
typedef int STDataType;
typedef struct Stack
{
 STDataType* _a;
 int _top; // 栈顶
 int _capacity; // 容量
}Stack;
// 初始化栈
void StackInit(Stack* ps);
// 入栈
void StackPush(Stack* ps, STDataType data);
// 出栈
void StackPop(Stack* ps);
// 获取栈顶元素
STDataType StackTop(Stack* ps);
// 获取栈中有效元素个数
int StackSize(Stack* ps);
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
int StackEmpty(Stack* ps);
// 销毁栈
void StackDestroy(Stack* ps); 

2.队列

2.1队列的概念

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出 FIFO(First In First Out)

入队列:进行插入操作的一端称为队尾

出队列:进行删除操作的一端称为队头

2.2队列的结构

13ddd835d5c646b4b8af0b575603f977.png

2.3队列的实现

队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数 组头上出数据,效率会比较低。

image.png

2.3.1队列的接口总览

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
typedef int QDataType;
typedef struct QueueNode
{
  struct QueueNode* next;
  QDataType data;
}QNode;
typedef struct Queue
{
  QNode* head;
  QNode* tail;
  int size;
}Que;
void QueueInit(Que* pq);
void QueueDestroy(Que* pq);
void QueuePush(Que* pq, QDataType x);
void QueuePop(Que* pq);
QDataType QueueFront(Que* pq);
QDataType QueueBack(Que* pq);
bool QueueEmpty(Que* pq);
int QueueSize(Que* pq);


在这里使用单链表,作为队列结构。队列特点就是先进先出,但是单链表比较适合头插头删,并不适合尾删,所以我们创建另一个结构体记录尾结点和头节点,这样也可以避免使用二级指针增加难度。

2.3.2队列的初始化

初始化我们先把所有内容设置成NULL或0即可

void QueueInit(Que* pq)
{
  pq->head = pq->tail = NULL;
  pq->size = 0;
}

2.3.3入队

入队其实就是链表中的尾插!!!

void QueuePush(Que* pq, QDataType x)
{
  assert(pq);
  QNode* newnode = (QNode*)malloc(sizeof(QNode));
  if (newnode == NULL)
  {
    perror("malloc");
    exit(-1);
  }
  newnode->data = x;
  newnode->next = NULL;
  if (pq->tail == NULL)
  {
    pq->head = pq->tail = newnode;
  }
  else
  {
    pq->tail->next = newnode;
    pq->tail = newnode;
  }
  pq->size++;
}


我们一定要记得将记录首尾的结构体进行赋值,分情况进行,不要忘记更新哦!!!

2.3.4出队

出队就是链表中的头删

void QueuePop(Que* pq)
{
  assert(pq);
  assert(!QueueEmpty(pq));
  if (pq->head->next == NULL)
  {
    free(pq->head);
    pq->head = pq->tail = NULL;
  }
  else
  {
    QNode* next = pq->head->next;
    free(pq->head);
    pq->head = next;
  }
  pq->size--;
}


我们一定要注意当删到只有一个内容节点时,要处理尾指针指向NULL。

2.3.5获取对头元素

只需要判断队列存指针正确且不为空,直接返回head指向的data即可。 QueueEmpty函数就是判断队列是否为空的函数,在后面有代码展示!!!

QDataType QueueFront(Que* pq)
{
  assert(pq);
  assert(!QueueEmpty(pq));
  return ps->head->data;
}

2.3.6获取对尾元素

尾元素获取与首元素基本相同。

QDataType QueueBack(Que* pq)
{
  assert(pq);
  assert(!QueueEmpty(pq));
  return ps->tail->data;
}

2.3.7判断是否为空

bool QueueEmpty(Que* pq)
{
  assert(pq);
  return ps->tail == NULL;
}

2.3.8队列销毁

一定不要忘记创建的存放头尾节点的指针结构体,也要将它们置空

void QueueDestroy(Que* pq)
{
  assert(pq);
  QNode* cur = pq->head;
  while (cur)
  {
    QNode* next = cur->next;
    free(cur);
    cur = next;
  }
  pq->head = ps->tail = NULL;
  pq->size = 0;
}

3.拓展内容

实际中我们有时还会使用一种队列叫循环队列。如操作系统课程讲解生产者消费者模型 时可以就会使用循环队列。环形队列可以使用数组实现,也可以使用循环链表实现。

bd822a9d843e4919a3b16200a0a35444.png

a98b87ff813749b9bd0e2c285aac7011.png

栈、队列、顺序表、链表都是相似但不同的数据结构,我们在学习时一定要了解其中的差异,这样才能在以后更好的应用!!!


以上就是本节全部内容,感谢大家耐心观看,你们的三连支持是博主最大创作动力。

目录
相关文章
|
2天前
|
存储
栈与队列练习题
栈与队列练习题
|
2天前
|
存储 索引
操作数栈的字节码指令执行分析
操作数栈的字节码指令执行分析
|
2天前
|
算法 C++
D1. Range Sorting (Easy Version)(单调栈+思维)
D1. Range Sorting (Easy Version)(单调栈+思维)
|
2天前
|
人工智能
线段树最大连续子段板子😂单调栈
线段树最大连续子段板子😂单调栈
|
2天前
数据结构第四课 -----线性表之队列
数据结构第四课 -----线性表之队列
|
2天前
数据结构第四课 -----线性表之栈
数据结构第四课 -----线性表之栈
|
3天前
|
存储
栈数据结构详解
栈(stack)是一种线性数据结构,栈中的元素只能先入后出(First In Last Out,简称FILO)。最早进入的元素存放的位置叫作栈底(bottom),最后进入的元素存放的位置叫作栈顶 (top)。本文是对堆结构的通透介绍
|
3天前
|
存储 Java
数据结构奇妙旅程之栈和队列
数据结构奇妙旅程之栈和队列
|
4天前
|
算法
栈刷题记(二-用栈操作构建数组)
栈刷题记(二-用栈操作构建数组)
|
4天前
栈刷题记(一-有效的括号)
栈刷题记(一-有效的括号)
栈刷题记(一-有效的括号)