【数据结构】栈和队列的实现(C语言)

简介: 【数据结构】栈和队列的实现(C语言)

前言


栈和队列都是重要的线性结构,即在使用层面上收到限制而发挥特殊作用的线性表。掌握这两种结构在不同情景下亦会发挥重要作用。


定义


栈保持着后进先出的原则,正因如此在插入数据的时候要求只能从一段插入,称为栈顶;相反另一端就被称为栈底。而插入数据叫做进栈,删除数据叫做出栈,并且都只能在栈顶进行操作。

image.png

了解了栈的定义,现在就应着手于如何实现。应考虑清楚根据其性质我们要构造出一个怎样的结构来实现栈。


我们需要在一端插入或删除数据并及时对其访问,因此我们这里使用动态顺序表来实现。在这个结构中 top 由于初始值设定为 0 所以代表的是栈顶下一位的下标,也可以设置为-1,则代表的是栈顶的下标。使用capacity是为了时刻检测内存的大小便于及时开辟。

image.png

实现


这次要实现的就是下面几个函数,对应了该数据结构时经常使用的操作。

image.png

初始化


首先为内部储存的数据开辟一段空间,(这里我设置的初始值是4根据需要可以进行调整),开辟成功后,用结构体内的指针指向这片空间,同时为 top capacity 赋上初始值。这样便完成了栈的初始化。

image.png

增删查改


入栈

一切开始前首先应该判断传入的指针是否为空指针,避免出现对空指针解引用的情况。后判断这个空间是否已满,不然增大空间的容量。之前说过 top 指向的是栈顶的下一位,因此直接赋值后再对 top ++ 就可以了。

image.png

这里对容量的判定是分解到另一个函数内去的,其实直接写这个函数里面也是可以的毕竟只有这里需要判断容量是否满了。


从下标的角度上来说,top 是比栈顶多 1 的,由于下标的从 0 开始因此 top 正好可以表达此刻数据的数量。当 top 与 capacity 相等时,即开辟的空间已满。所以使用 realloc 调整新空间的大小(我把默认设为每次增加都是原来的两倍)。开辟成功后把新的地址给 data ,调整 capacity 为新容量的大小。

image.png

出栈

出栈的操作相当的简便,只要 top-- 这样未来访问的时候就不会再访问到,也没有必要再对其进行赋值,因为下次插入的时候就会用新值覆盖原来的值。但是断言还是要记得断言的。

image.png

获取栈顶元素

这里我们通过top访问到栈顶的元素之后并将其返回,就完成了栈顶元素的获取。

image.png

判空


这里使用了 bool 值要引用头文件 stdboo.h ,前面讲到 top 在栈中的意义就相当于内置数据的数量,即 top 不等于 0 则栈里就是还有数据的,所以返回false

image.png

销毁


栈结束使用后,要进行销毁避免内存泄漏。断言后释放data 后再将top跟capacity赋值为初始值,由于前面没有对结构体进行动态开辟,所以这里就不用释放。

image.png

队列


定义


队列与栈相反,栈是先进后出,而队列是先进先出,即从队头入队(插入数据)到队尾出队(删除数据),这个规则无法改变。

image.png

实现


在这里关于结构的选择就不是双选题了,因为无论选左边还是右边作为队尾,数组总是逃不过需要挪动数据的结果。反观链表就显得相对自由。

image.png

但只使用链表结构也不够完善,对于链表来说最痛苦的莫非是尾结点的访问了,但这里要频繁地对尾节点进行访问,不妨再使用一个结构体存储头尾结点,来表示一整个队列。这样处理后,若我们要对头结点进行更改也不需要使用到二级指针了,因为只是对结构体内的数据进行更改因此只需要结构体指针。为了统计大小更加方便也需要一个 size 表示大小。

image.png

如下为要实现的函数。

image.png

初始化


一开始的队列本为空,只需要将头尾结点制空, size 归零。就完成了插入数据前的准备。

image.png

为空的判定


为空的判定就相对简单,头尾结点正常情况下是同时为空的,也为了避免特殊情况即二者其中一个为空就可以判断整个队列是空的。

image.png

增删查改


入队

传入数值前先断言(养成好习惯),之后像链表那样开辟一个新节点并对其赋值。之后就要面临一个分岔口,队列是否为空,贸然地对头结点解引用就会出现错误,若为空则新节点就变成头结点,非空就链接在尾结点后,这之后自身成为尾结点。

6fdc7e5898a942afa79a377ffb7ba48a.png

出队

断言后出队需要做到的是:事先将下一结点保存起来,不然当前头结点释放后便无法访问到下一结点了。之后释放头结点,下一结点就变成了头结点,同时 size-- ,保证空间数量的准确避免越界访问。

image.png

访问队头队尾

直接访问头(尾)结点便是队头(尾)。

image.png

image.png

求大小


这里需要感谢我们在前面在结构内就存储了大小,不然还需要遍历链表才能得到队列的大小。

image.png

队列的销毁


同样有 malloc 就有 free ,在这里我们每个结点都是动态开辟出来的,所以都需要释放。就像链表释放那样,一边遍历一边释放。最后再把队列的结构体初始化就完成了。

image.png

好了,这次栈和队列的实现到这里就告一段落了。后面是这次的源码

源码


stack.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int STdatatype;
typedef struct Stack
{
  STdatatype* data;
  int top;
  int capacity;
}Stack;
//初始化
void StackInit(Stack* p);
//入栈
void StackPush(Stack* p, STdatatype data);
//依次出栈打印
void Stackprint(Stack* p);
//出栈
void StackPop(Stack* p);
//获取顶端元素
STdatatype StackTop(Stack* p);
//有效数据的个数
int StackSize(Stack* p);
//检测栈是否为空,不为空则返回0
bool StackEmpty(Stack* p);
//销毁栈
void StackDestroy(Stack* p);

stack.c

#include"stack.h"
void checkcapacity(Stack* p)
{
  STdatatype* newp;                       
  if (p->top == p->capacity)
  {
    newp = (STdatatype*)realloc(p->data, sizeof(Stack) * p->capacity * 2);
    if (newp == NULL)                 //开辟失败返回
    {
      perror(realloc);
      exit(-1);
    }
    p->data = newp;                   //把新开辟的空间给data
    p->capacity *= 2;                 //调整capacity为新值
  }
}
void StackInit(Stack* p)
{
  STdatatype* np = (STdatatype*)malloc(sizeof(STdatatype) * 4);   //开辟空间初始值为4
  if (np)
  {
    p->data = np;           //不为空赋值
  }
  p->top = 0;               //赋初值
  p->capacity = 4;
}
void StackPush(Stack* p, STdatatype x)
{
  assert(p);              //断言不为空
  checkcapacity(p);         //判断空间是否满了
  (p->data)[p->top] = x;        //赋值
  p->top++;
}
void Stackprint(Stack* p)
{
  int i = p->top - 1;
  while (i >= 0)
  {
    printf("%d ", (p->data)[i--]);
  }
  printf("\n");
}
void StackPop(Stack* p)
{
  assert(p);              //断言p不为空指针
  assert(p->top);         //断言栈内数据不为空
  p->top--;
}
STdatatype StackTop(Stack* p)
{
  assert(p);                           //断言
  int top = p->top - 1;                //找到栈顶
  return (p->data)[top];               //返回栈顶的数值
}
bool StackEmpty(Stack* p)
{
  assert(p);
  if (p->top != 0)
  {
    return false;
  }
  return true;
}
void StackDestroy(Stack* p)
{
  assert(p);                //断言
  assert(p->data);
  free(p->data);            //释放数据
  p->data = NULL;           //对data制空
  p->top = 0;               //回归初始值
  p->capacity = 0;
}

queue.h

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int Qdatatype;
typedef struct Qnode
{
  Qdatatype data;          //数据
  struct Queue* next;      //指向下个结点
}Qnode;
typedef struct Queue
{
  Qnode* head;             //队列的头
  Qnode* tail;             //队列的尾
  int size;                //大小
}Queue;
void Queueinit(Queue* p);                     //初始化
void Queuepush(Queue* p, Qdatatype x);        //入队
void Queuepop(Queue* p);                      //出队
bool QueueEmpty(Queue* p);                    //检测为空
Qdatatype Queuefront(Queue* p);               //访问队头的数据
Qdatatype Queueback(Queue* p);                //访问队尾的数据
int Queuesize(Queue* p);                      //求队列的大小

queue.c

#include "queue.h"
void Queueinit(Queue* p)
{
  p->head = NULL;           //头尾结点制空
  p->tail = NULL;
  p->size = 0;              //数据量为0
}
bool QueueEmpty(Queue* p)
{
  assert(p);
  return p->head == NULL || p->tail == NULL;    //二者一个为空则队列为空
}
void Queuepush(Queue* p, Qdatatype x)
{
  assert(p);                //断言
  Qnode* newnode = (Qnode*)malloc(sizeof(Qnode));   //开辟新结点
  if (newnode == NULL)              //开辟失败返回
  {
    perror(malloc);
    exit(-1);
  }
  newnode->data = x;                //赋值
  newnode->next = NULL;
  if (p->head == NULL)              //队列为空的情况
  {
    p->head = p->tail = newnode;
  }
  else
  {
    p->tail->next = newnode;      //链接
    p->tail = newnode;
  }
  p->size++;                        //对size进行处理
}
void Queuepop(Queue* p)
{
  assert(p);                      //断言p不为空
  assert(!QueueEmpty(p));         //断言队列不为空
  Qnode* next = p->head->next;    //存储下一结点
  free(p->head);                  //释放当先头结点
  p->head = next;                 //下一结点变成头结点
  p->size--;                      //对size进行处理
}
Qdatatype Queuefront(Queue* p)
{
  assert(p);
  assert(!QueueEmpty(p));         //断言不为空
  return p->head->data;           //头结点存储的就是队头的数据
}
void QueueDestroy(Queue* p)
{
  assert(p);
  Qnode* cur = p->head;
  while (cur)
  {
    Qnode* next = cur->next;
    free(cur);
    cur = next;
  }
  p->head = p->tail = NULL;       //头尾结点制空
  p->size = 0;                    //大小归0
}
Qdatatype Queueback(Queue* p)
{
  assert(p);
  assert(!QueueEmpty(p));
  return p->tail->data;               //尾结点存储的就是队尾的数据
}
int Queuesize(Queue* p)
{
  assert(p);
  return p->size;             
}
目录
相关文章
|
22天前
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
34 1
|
1月前
|
存储 算法 搜索推荐
【趣学C语言和数据结构100例】91-95
本文涵盖多个经典算法问题的C语言实现,包括堆排序、归并排序、从长整型变量中提取偶数位数、工人信息排序及无向图是否为树的判断。通过这些问题,读者可以深入了解排序算法、数据处理方法和图论基础知识,提升编程能力和算法理解。
45 4
|
1月前
|
存储 机器学习/深度学习 搜索推荐
【趣学C语言和数据结构100例】86-90
本文介绍并用C语言实现了五种经典排序算法:直接插入排序、折半插入排序、冒泡排序、快速排序和简单选择排序。每种算法都有其特点和适用场景,如直接插入排序适合小规模或基本有序的数据,快速排序则适用于大规模数据集,具有较高的效率。通过学习这些算法,读者可以加深对数据结构和算法设计的理解,提升解决实际问题的能力。
43 4
|
24天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
44 5
|
22天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
54 1
|
1月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
187 9
|
1月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
32 1
|
1月前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
|
1月前
|
存储 JavaScript 前端开发
执行上下文和执行栈
执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。
|
1月前
|
存储
系统调用处理程序在内核栈中保存了哪些上下文信息?
【10月更文挑战第29天】系统调用处理程序在内核栈中保存的这些上下文信息对于保证系统调用的正确执行和用户程序的正常恢复至关重要。通过准确地保存和恢复这些信息,操作系统能够实现用户模式和内核模式之间的无缝切换,为用户程序提供稳定、可靠的系统服务。
51 4