【C语言数据结构(基础版)】第四站:栈和队列

简介: 【C语言数据结构(基础版)】第四站:栈和队列

一.栈的表示和实现

1.栈的概念及结构

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

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

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

2.栈的实现

从上面我们也可以看出来,栈的实现一般可以使用数组或者链表实现,相对而言,其实数组的结构实现更优一些,因为数组在尾上插入数据的代价比较小

二、栈的实现

1.栈的声明和定义

根据我们上面的分析,我们决定使用顺序表来实现栈。所以它的声明如下

typedef int STDateType;
typedef struct Stack
{
  STDateType* a;
  int top;
  int capacity;
}Stack;

2.栈的初始化

它的声明如下

//栈的初始化
void StackInit(Stack* ps);

它的函数实现也很简单

//栈的初始化
void StackInit(Stack* ps)
{
  assert(ps);
  ps->a = (STDateType*)malloc(4 * sizeof(STDateType));
  if (ps->a == NULL)
  {
    printf("malloc is fail\n");
    exit(-1);
  }
  ps->capacity = 4;
  ps->top = 0;
}

注意这里的top可以为0也可以为-1

这两种其实也是有区别的,

如果top初始化为0的化,那就意味着top指向栈顶元素的下一个

如果top初始化欸-1的话,那么意味着top指向栈顶元素

3.栈的销毁

创建好那么必要要有销毁

销毁的声明为

//栈的销毁
void StackDestory(Stack* ps);

实现为

//栈的销毁
void StackDestory(Stack* ps)
{
  assert(ps);
  free(ps->a);
  ps->a = NULL;
  ps->capacity = ps->top = 0;
}

4.入栈

这个也非常简单,函数声明为

//入栈
void StackPush(Stack* ps, STDateType x);

实现为

//入栈
void StackPush(Stack* ps, STDateType x)
{
  assert(ps);
  if (ps->top == ps->capacity)
  {
    //扩容
    STDateType* tmp = (STDateType*)realloc(ps->a, sizeof(STDateType) * 2 * ps->capacity);
    if (tmp == NULL)
    {
      printf("realloc is fail\n");
      exit(-1);
    }
    ps->a = tmp;
    ps->capacity *= 2;
  }
  ps->a[ps->top] = x;
  ps->top++;
}

5.出栈

函数声明为

//出栈
void StackPop(Stack* ps);

函数实现为

//出栈
void StackPop(Stack* ps)
{
  assert(ps);
  //栈为空了,还想要继续删除直接报错
  assert(ps->top > 0);
  ps->top--;
}

6.返回栈顶元素

这是函数声明

//取出栈顶元素
STDateType StackTop(Stack* ps);

这是函数实现

//取出栈顶元素
STDateType StackTop(Stack* ps)
{
  assert(ps);
  //栈为空,还继续调用的话直接报错
  assert(ps->top > 0);
  return ps->a[ps->top - 1];
}

7.返回栈的元素个数

这是函数声明

//栈的元素个数
int StackSize(Stack* ps);

这是函数实现

//栈的元素个数
int StackSize(Stack* ps)
{
  assert(ps);
  return ps->top;
}

8.栈是否为空

这是函数声明

//栈是否为空
bool StackEmpty(Stack* ps);

这是函数实现

//栈是否为空
bool StackEmpty(Stack* ps)
{
  assert(ps);
  return (ps->top == 0);
}

9.测试

#define _CRT_SECURE_NO_WARNINGS 1
#include"Stack.h"
int main()
{
  Stack s;
  StackInit(&s);
  StackPush(&s, 1);
  StackPush(&s, 2);
  StackPush(&s, 3);
  printf("%d ", StackTop(&s));
  StackPop(&s);
  printf("%d ", StackTop(&s));
  StackPop(&s);
  StackPush(&s, 4);
  StackPush(&s, 5);
  while (!StackEmpty(&s))
  {
    printf("%d ", StackTop(&s));
    StackPop(&s);
  }
  StackDestory(&s);
}

运行结果为

三、栈的完整代码

Test.c文件

#define _CRT_SECURE_NO_WARNINGS 1
#include"Stack.h"
int main()
{
  Stack s;
  StackInit(&s);
  StackPush(&s, 1);
  StackPush(&s, 2);
  StackPush(&s, 3);
  printf("%d ", StackTop(&s));
  StackPop(&s);
  printf("%d ", StackTop(&s));
  StackPop(&s);
  StackPush(&s, 4);
  StackPush(&s, 5);
  while (!StackEmpty(&s))
  {
    printf("%d ", StackTop(&s));
    StackPop(&s);
  }
  StackDestory(&s);
}

Stack.h文件

#pragma once
#include<stdio.h>
#include<stdbool.h>
#include<assert.h>
#include<string.h>
#include<stdlib.h>
typedef int STDateType;
typedef struct Stack
{
  STDateType* a;
  int top;
  int capacity;
}Stack;
//栈的初始化
void StackInit(Stack* ps);
//栈的销毁
void StackDestory(Stack* ps);
//入栈
void StackPush(Stack* ps, STDateType x);
//出栈
void StackPop(Stack* ps);
//取出栈顶元素
STDateType StackTop(Stack* ps);
//栈的元素个数
int StackSize(Stack* ps);
//栈是否为空
bool StackEmpty(Stack* ps);

Stack.c文件

#define _CRT_SECURE_NO_WARNINGS 1
#include"Stack.h"
//栈的初始化
void StackInit(Stack* ps)
{
  assert(ps);
  ps->a = (STDateType*)malloc(4 * sizeof(STDateType));
  if (ps->a == NULL)
  {
    printf("malloc is fail\n");
    exit(-1);
  }
  ps->capacity = 4;
  ps->top = 0;
}
//栈的销毁
void StackDestory(Stack* ps)
{
  assert(ps);
  free(ps->a);
  ps->a = NULL;
  ps->capacity = ps->top = 0;
}
//入栈
void StackPush(Stack* ps, STDateType x)
{
  assert(ps);
  if (ps->top == ps->capacity)
  {
    //扩容
    STDateType* tmp = (STDateType*)realloc(ps->a, sizeof(STDateType) * 2 * ps->capacity);
    if (tmp == NULL)
    {
      printf("realloc is fail\n");
      exit(-1);
    }
    ps->a = tmp;
    ps->capacity *= 2;
  }
  ps->a[ps->top] = x;
  ps->top++;
}
//出栈
void StackPop(Stack* ps)
{
  assert(ps);
  //栈为空了,还想要继续删除直接报错
  assert(ps->top > 0);
  ps->top--;
}
//取出栈顶元素
STDateType StackTop(Stack* ps)
{
  assert(ps);
  //栈为空,还继续调用的话直接报错
  assert(ps->top > 0);
  return ps->a[ps->top - 1];
}
//栈的元素个数
int StackSize(Stack* ps)
{
  assert(ps);
  return ps->top;
}
//栈是否为空
bool StackEmpty(Stack* ps)
{
  assert(ps);
  return (ps->top == 0);
}

四、队列的表示和实现

1.队列的概念和结构

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

2.队列的实现

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

五、队列的实现

1.队列的声明和定义

我们想要使用链表的结构来实现队列,但是由于单链表的尾插过于麻烦,不妨我们直接声明头节点和尾结点来控制队列

typedef int QDateType;
typedef struct QueueNode
{
  struct QueueNode* next;
  QDateType date;
}QNode;
typedef struct Queue
{
  QNode* head;
  QNode* tail;
}Queue;

如上所示,我们先定义了一个队列结点的结构体,里面包含了指向下一个结点的指针和一个队列的数据。然后我们定义了一个队列结构体,我们主要是使用队列这个结构体,它里面有头结点和尾结点,有头有尾刚好确定一个队列。

2.队列的初始化

接下来让我们先来实现一下队列的初始化

函数声明为

//队列初始化
void QueueInit(Queue* pq);

实现为

//队列的初始化
void QueueInit(Queue* pq)
{
  assert(pq);
  pq->head = NULL;
  pq->tail = NULL;
}

其实对这个实现,建议与前面的一些LeetCode题目和单链表的实现进行对比,我们可以看出来这里的妙处。

3.队列的销毁

有始有终,我们来实现一下队列的销毁

//队列的销毁
void QueueDestory(Queue* pq);

函数实现为

//队列的销毁
void QueueDestory(Queue* pq)
{
  assert(pq);
  QNode* cur = pq->head;
  while (cur)
  {
    QNode* next = cur->next;
    free(cur);
    cur = next;
  }
  pq->head = pq->tail = NULL;
}

4.队列的插入

函数声明为

//队列的插入
void QueuePush(Queue* pq, QDateType x);

函数实现为

//队列的插入
void QueuePush(Queue* pq, QDateType x)
{
  assert(pq);
  QNode* newnode = (QNode*)malloc(sizeof(QNode));
  if (newnode == NULL)
  {
    printf("malloc is fail\n");
    exit(-1);
  }
  //对新节点进行初始化
  newnode->date = x;
  newnode->next = NULL;
  //对新结点进行连接
  if (pq->head == NULL)
  {
    //头结点是空的,也就是第一个数据的插入
    pq->head = pq->tail = newnode;
  }
  else
  {
    //非第一个数据的插入
    pq->tail->next = newnode;
    pq->tail = newnode;
  }
}

5.队列的删除

函数声明为

//队列的删除
void QueuePop(Queue* pq);

函数实现为

//队列的删除
void QueuePop(Queue* pq)
{
  assert(pq);
  //确保队列不是空的队列
  assert(pq->head);
  //如果只有一个结点,防止tail形成野指针
  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;
  }
}

6.队列的判空

函数声明

//判断队列是否为空
bool QueueEmpty(Queue* pq);

函数实现

//判断队列是否为空
bool QueueEmpty(Queue* pq)
{
  assert(pq);
  return pq->head == NULL;
}

7.取出队头的数据

函数声明

//取出队头的数据
QDateType QueueFront(Queue* pq);

函数实现

//取出队头的数据
QDateType QueueFront(Queue* pq)
{
  assert(pq);
  assert(pq->head);
  return pq->head->date;
}

8.取出队尾的数据

函数声明

//取出队尾的数据
QDateType QueueBack(Queue* pq);

函数实现

//取出队尾的数据
QDateType QueueBack(Queue* pq)
{
  assert(pq);
  assert(pq->head);
  return pq->tail->date;
}

9.队列数据的个数

函数声明

//取出数据的个数
int QueueSize(Queue* pq);

函数实现

//取出数据的个数
int QueueSize(Queue* pq)
{
  assert(pq);
  int size = 0;
  QNode* cur = pq->head;
  while (cur)
  {
    cur = cur->next;
    size++;
  }
  return size;
}

10.测试

void TestQueue()
{
  Queue q;
  QueueInit(&q);
  QueuePush(&q, 1);
  QueuePush(&q, 2);
  QueuePush(&q, 3);
  QueuePush(&q, 4);
  QueuePush(&q, 5);
  while (!QueueEmpty(&q))
  {
    printf("%d ", QueueFront(&q));
    QueuePop(&q);
  }
  printf("\n");
  QueueDestory(&q);
}
int main()
{
  //TestStack();
  TestQueue();
}

运行结果为

六、队列完整代码

Test.c

void TestQueue()
{
  Queue q;
  QueueInit(&q);
  QueuePush(&q, 1);
  QueuePush(&q, 2);
  QueuePush(&q, 3);
  QueuePush(&q, 4);
  QueuePush(&q, 5);
  while (!QueueEmpty(&q))
  {
    printf("%d ", QueueFront(&q));
    QueuePop(&q);
  }
  printf("\n");
  QueueDestory(&q);
}
int main()
{
  //TestStack();
  TestQueue();
}

Queue.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<stdbool.h>
#include<assert.h>
typedef int QDateType;
typedef struct QueueNode
{
  struct QueueNode* next;
  QDateType date;
}QNode;
typedef struct Queue
{
  QNode* head;
  QNode* tail;
}Queue;
//队列初始化
void QueueInit(Queue* pq);
//队列的销毁
void QueueDestory(Queue* pq);
//队列的插入
void QueuePush(Queue* pq, QDateType x);
//队列的删除
void QueuePop(Queue* pq);
//取出队头的数据
QDateType QueueFront(Queue* pq);
//取出队尾的数据
QDateType QueueBack(Queue* pq);
//取出数据的个数
int QueueSize(Queue* pq);
//判断队列是否为空
bool QueueEmpty(Queue* pq);

Queue.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Queue.h"
//队列的初始化
void QueueInit(Queue* pq)
{
  assert(pq);
  pq->head = NULL;
  pq->tail = NULL;
}
//队列的销毁
void QueueDestory(Queue* pq)
{
  assert(pq);
  QNode* cur = pq->head;
  while (cur)
  {
    QNode* next = cur->next;
    free(cur);
    cur = next;
  }
  pq->head = pq->tail = NULL;
}
//队列的插入
void QueuePush(Queue* pq, QDateType x)
{
  assert(pq);
  QNode* newnode = (QNode*)malloc(sizeof(QNode));
  if (newnode == NULL)
  {
    printf("malloc is fail\n");
    exit(-1);
  }
  //对新节点进行初始化
  newnode->date = x;
  newnode->next = NULL;
  //对新结点进行连接
  if (pq->head == NULL)
  {
    //头结点是空的,也就是第一个数据的插入
    pq->head = pq->tail = newnode;
  }
  else
  {
    //非第一个数据的插入
    pq->tail->next = newnode;
    pq->tail = newnode;
  }
}
//队列的删除
void QueuePop(Queue* pq)
{
  assert(pq);
  //确保队列不是空的队列
  assert(pq->head);
  //如果只有一个结点,防止tail形成野指针
  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;
  }
} 
//判断队列是否为空
bool QueueEmpty(Queue* pq)
{
  assert(pq);
  return pq->head == NULL;
}
//取出队头的数据
QDateType QueueFront(Queue* pq)
{
  assert(pq);
  assert(pq->head);
  return pq->head->date;
}
//取出队尾的数据
QDateType QueueBack(Queue* pq)
{
  assert(pq);
  assert(pq->head);
  return pq->tail->date;
}
//取出数据的个数
int QueueSize(Queue* pq)
{
  assert(pq);
  int size = 0;
  QNode* cur = pq->head;
  while (cur)
  {
    cur = cur->next;
    size++;
  }
  return size;
}

总结

本节实现了栈和队列的实现,难度不大,希望大家都能学会

如果对你有帮助,不要忘记点赞加收藏哦!!!

想获得更多优质的内容,一定要记得关注我哦!!!

相关文章
|
11月前
|
前端开发 Java
java实现队列数据结构代码详解
本文详细解析了Java中队列数据结构的实现,包括队列的基本概念、应用场景及代码实现。队列是一种遵循“先进先出”原则的线性结构,支持在队尾插入和队头删除操作。文章介绍了顺序队列与链式队列,并重点分析了循环队列的实现方式以解决溢出问题。通过具体代码示例(如`enqueue`入队和`dequeue`出队),展示了队列的操作逻辑,帮助读者深入理解其工作机制。
420 1
|
9月前
|
编译器 C语言 C++
栈区的非法访问导致的死循环(x64)
这段内容主要分析了一段C语言代码在VS2022中形成死循环的原因,涉及栈区内存布局和数组越界问题。代码中`arr[15]`越界访问,修改了变量`i`的值,导致`for`循环条件始终为真,形成死循环。原因是VS2022栈区从低地址到高地址分配内存,`arr`数组与`i`相邻,`arr[15]`恰好覆盖`i`的地址。而在VS2019中,栈区先分配高地址再分配低地址,因此相同代码表现不同。这说明编译器对栈区内存分配顺序的实现差异会导致程序行为不一致,需避免数组越界以确保代码健壮性。
195 0
栈区的非法访问导致的死循环(x64)
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
713 77
232.用栈实现队列,225. 用队列实现栈
在232题中,通过两个栈(`stIn`和`stOut`)模拟队列的先入先出(FIFO)行为。`push`操作将元素压入`stIn`,`pop`和`peek`操作则通过将`stIn`的元素转移到`stOut`来实现队列的顺序访问。 225题则是利用单个队列(`que`)模拟栈的后入先出(LIFO)特性。通过多次调整队列头部元素的位置,确保弹出顺序符合栈的要求。`top`操作直接返回队列尾部元素,`empty`判断队列是否为空。 两题均仅使用基础数据结构操作,展示了栈与队列之间的转换逻辑。
|
算法 调度 C++
STL——栈和队列和优先队列
通过以上对栈、队列和优先队列的详细解释和示例,希望能帮助读者更好地理解和应用这些重要的数据结构。
304 11
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
497 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。
|
搜索推荐 C语言
数据结构(C语言)之对归并排序的介绍与理解
归并排序是一种基于分治策略的排序算法,通过递归将数组不断分割为子数组,直到每个子数组仅剩一个元素,再逐步合并这些有序的子数组以得到最终的有序数组。递归版本中,每次分割区间为[left, mid]和[mid+1, right],确保每两个区间内数据有序后进行合并。非递归版本则通过逐步增加gap值(初始为1),先对单个元素排序,再逐步扩大到更大的区间进行合并,直至整个数组有序。归并排序的时间复杂度为O(n*logn),空间复杂度为O(n),且具有稳定性,适用于普通排序及大文件排序场景。
|
C++
【C++数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】
【数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】(1)遇到左括号:进栈Push()(2)遇到右括号:若栈顶元素为左括号,则出栈Pop();否则返回false。(3)当遍历表达式结束,且栈为空时,则返回true,否则返回false。本关任务:编写一个程序利用栈判断左、右圆括号是否配对。为了完成本关任务,你需要掌握:栈对括号的处理。(1)遇到左括号:进栈Push()开始你的任务吧,祝你成功!测试输入:(()))
358 7
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
1161 10