【数据结构】栈和队列的实现(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;             
}
目录
相关文章
|
16天前
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
28 1
|
25天前
|
存储 算法 搜索推荐
【趣学C语言和数据结构100例】91-95
本文涵盖多个经典算法问题的C语言实现,包括堆排序、归并排序、从长整型变量中提取偶数位数、工人信息排序及无向图是否为树的判断。通过这些问题,读者可以深入了解排序算法、数据处理方法和图论基础知识,提升编程能力和算法理解。
41 4
|
25天前
|
存储 机器学习/深度学习 搜索推荐
【趣学C语言和数据结构100例】86-90
本文介绍并用C语言实现了五种经典排序算法:直接插入排序、折半插入排序、冒泡排序、快速排序和简单选择排序。每种算法都有其特点和适用场景,如直接插入排序适合小规模或基本有序的数据,快速排序则适用于大规模数据集,具有较高的效率。通过学习这些算法,读者可以加深对数据结构和算法设计的理解,提升解决实际问题的能力。
38 4
|
18天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
40 5
|
17天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
43 1
|
C语言
C语言队列实现
一,简介 开发环境是VC6.0,实现了一个基于C语言的队列。 主要功能,入队、出队、显示当前队列元素。
128 0
|
14天前
|
存储 C语言 开发者
【C语言】字符串操作函数详解
这些字符串操作函数在C语言中提供了强大的功能,帮助开发者有效地处理字符串数据。通过对每个函数的详细讲解、示例代码和表格说明,可以更好地理解如何使用这些函数进行各种字符串操作。如果在实际编程中遇到特定的字符串处理需求,可以参考这些函数和示例,灵活运用。
34 10
|
14天前
|
存储 程序员 C语言
【C语言】文件操作函数详解
C语言提供了一组标准库函数来处理文件操作,这些函数定义在 `<stdio.h>` 头文件中。文件操作包括文件的打开、读写、关闭以及文件属性的查询等。以下是常用文件操作函数的详细讲解,包括函数原型、参数说明、返回值说明、示例代码和表格汇总。
34 9
|
14天前
|
存储 Unix Serverless
【C语言】常用函数汇总表
本文总结了C语言中常用的函数,涵盖输入/输出、字符串操作、内存管理、数学运算、时间处理、文件操作及布尔类型等多个方面。每类函数均以表格形式列出其功能和使用示例,便于快速查阅和学习。通过综合示例代码,展示了这些函数的实际应用,帮助读者更好地理解和掌握C语言的基本功能和标准库函数的使用方法。感谢阅读,希望对你有所帮助!
29 8
|
14天前
|
C语言 开发者
【C语言】数学函数详解
在C语言中,数学函数是由标准库 `math.h` 提供的。使用这些函数时,需要包含 `#include <math.h>` 头文件。以下是一些常用的数学函数的详细讲解,包括函数原型、参数说明、返回值说明以及示例代码和表格汇总。
34 6