目录
[1.1 栈的概念及结构](#1.1 栈的概念及结构)
[1.2 栈的底层数据结构选择](#1.2 栈的底层数据结构选择)
[1.2 数据结构设计代码(栈的实现)](#1.2 数据结构设计代码(栈的实现))
[1.3 接口函数实现代码](#1.3 接口函数实现代码)
[1.4 测试函数](#1.4 测试函数)
[2. 队列](#2. 队列)
[2.1 队列概念及结构](#2.1 队列概念及结构)
[2.2 队列的底层数据结构的选择](#2.2 队列的底层数据结构的选择)
[2.2 数据结构设计代码(队列的实现)](#2.2 数据结构设计代码(队列的实现))
[2.3 接口函数](#2.3 接口函数)
[2.4 测试函数](#2.4 测试函数)
[3. 栈和队列的源代码](#3. 栈和队列的源代码)
[3.1 栈](#3.1 栈)
[3.2 队列](#3.2 队列)
前言
这篇文章是关于栈和队列的C语言实现,干货满满,大家可以边看边手写代码,最后附上栈和队列的参考代码。
1.栈
1.1 栈的概念及结构
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素 操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶 。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。
这个数据结构可以理解为炸串。在最开始,插入丸子时,要插到这根签的最底部,就是入栈 。开始吃的时候,一般从最上面的丸子入手,就是出栈。

1.2 栈的底层数据结构选择
我们学过顺序表和链表两种数据结构,用那种会更好呢?
- 相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。不用挪动数据,删除操作也很简单。
- 而链表如果要入栈,就是链表的尾插,我们需要找到链表的尾结点,效率低。出栈也是一样。


1.2 数据结构设计代码(栈的实现)
在用C语言实现栈各种接口函数,需要创建三个文件,分别是STtest.c,Stack.c和Stack.h。
- STtest.c文件用来测试接口函数是否能成功。
- Stack.c文件里面放的是各种接口函数实现的代码
- Stack.h文件放置各种头文件的声明,栈数据结构的设计和接口函数的声明。下面的代码就是放在此文件中。
cpp
typedef int STDataType;
// 下面是定长的静态栈的结构,实际中一般不实用,所以我们主要实现下面的支持动态增长的栈
typedef int STDataType;
#define N 10
typedef struct Stack
{
STDataType _a[N];
int _top; // 栈顶
}Stack;
// 支持动态增长的栈
typedef struct Stack
{
STDataType* a;
int top; //栈顶
int capacity; //容量
}ST;
//初始化栈
void StackInit(ST* ps);
//销毁栈
void StackDestroy(ST* ps);
//压栈
void StackPush(ST* ps, STDataType x);
//出栈
void StackPop(ST* ps);
//获取栈顶元素
STDataType StackTop(ST* ps);
//获取栈中有效元素个数
int StackSize(ST* ps);
//检测栈是否为空,为空返回true
bool StackEmpty(ST* ps);
1.3 接口函数实现代码
(1)初始化栈
初始化栈一开始要断言,断言来判断ps指针是否为空,如果为空会直接终止程序并给出提示,方便之后的调试,之后的断言不再赘述。指针指向空,内部整型数据赋值为0。但是top得注意,top其实也是个下标。看下面的注释,top可以有两种赋值方式,只是表示不同意思。
cpp
void StackInit(ST* ps)
{
assert(ps);
ps->a = NULL;
ps->top = 0;//表示指向栈顶的下一个元素
//ps->top = -1 ///表示指向栈顶元素
ps->capacity = 0;
}
(2)销毁栈
因为我们是以数组为基础,是动态开辟的连续空间,直接释放掉ps中的数组指针,并要置为空。
cpp
void StackDestroy(ST* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;//易错
ps->top = ps->capacity = 0;
}
(3)压栈
栈的压栈操作跟顺序表的尾插类似,先要检查内部空间容量是否足够,一开始给四个该数据结构的空间,之后的按两倍来扩容。最后就是插入数据,栈顶减一。
cpp
void StackPush(ST* ps, STDataType x)
{
assert(ps);
if (ps->top == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
STDataType* tmp = realloc(ps->a, sizeof(STDataType) * newCapacity);
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
ps->a = tmp;
ps->capacity = newCapacity;
}
ps->a[ps->top] = x;
ps->top++;
}
(4)出栈
这次不仅要判断ps是否不为空指针,还要判断栈内是否有元素,这样才可以删除元素,让栈顶减一即可。
cpp
void StackPop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
ps->top--;
}
(5)获取栈顶元素
这里的判断跟上个接口函数一样,只有栈内有元素,才能取栈顶元素。
cpp
STDataType StackTop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
return ps->a[ps->top - 1];
}
(6)获取栈中有效元素的个数
此时的top表示的是栈顶的下一个元素的下标,刚好就是元素个数,直接返回栈顶top
cpp
int StackSize(ST* ps)
{
assert(ps);
return ps->top;
}
(7)检测栈是否为空
很多人会写个if语句判断,其实可以直接像下面一样写,ps->top == 0变成一个判断语句,如果为真返回一个部位0的数,如果为假返回0。
cpp
bool StackEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;
}
1.4 测试函数
我们写两个测试函数,第一个测试函数,是出栈和入栈的操作,可以调试查看。第二个测试函数是栈的数据打印,需要使用StackTop获取栈顶元素,然后将打印的数据出栈。
cpp
void TestStack1()
{
ST st;
StackInit(&st);
StackPush(&st, 1);
StackPush(&st, 2);
StackPush(&st, 3);
StackPush(&st, 4);
StackPop(&st);
StackPop(&st);
StackPop(&st);
StackPop(&st);
StackPop(&st);
StackDestroy(&st);
}
void TestStack2()
{
ST st;
StackInit(&st);
StackPush(&st, 1);
StackPush(&st, 2);
StackPush(&st, 3);
StackPush(&st, 4);
while (!StackEmpty(&st))
{
printf("%d ", StackTop(&st));
StackPop(&st);
}
StackDestroy(&st);
}
int main()
{
TestStack1();
TestStack2();
return 0;
}
输出结果:

2. 队列
2.1 队列概念及结构
队列:只允许在一端 进行插入数据操作,在另一端 进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)
入队列:进行插入操作的一端称为队尾
出队列:进行删除操作的一端称为队头
这个数据结构可以理解为在银行取号,先取号的人,就是入队列。然后先取到号的人,就先去窗口处理业务,这就是出队列。

2.2 队列的底层数据结构的选择
跟栈一样,队列也可以用顺序表和链表来实现,那哪一个更好呢?
- 相较于数组,使用链表更优一些。队列的入队和出队,分别是链表的尾插和头删,不过我们会创建两个指针指向头结点和尾结点,所以不会有时间的消耗。
- 而数组在头部插入和删除数据,都要挪动数据,效率较低。
链表的数据结构存储的是一个数据变量,和指向下一个结点的指针变量。我们入队列和出队列分别在队尾和队头,所以我们在队列的数据结构定义两个链表的指针变量head和tail,表示队头和队尾

2.2 数据结构设计代码(队列的实现)
跟栈的实现一样,首先要创建三个文件Qtest.c,Queue.c和Queue.h。
- Qtest.c文件用来测试接口函数是否能成功。
- Queue.c文件里面放的是各种接口函数实现的代码
- Queue.h文件放置各种头文件的声明,栈数据结构的设计和接口函数的声明。下面的代码就是放在此文件中。
cpp
// 链式结构:表示队列
typedef int QDataType;
typedef struct QueueNode
{
struct QueueNode* next;
QDataType data;
}QueueNode;
// 队列的结构
typedef struct Queue
{
QueueNode* head;
QueueNode* tail;
}Queue;
//初始化队列
void QueueInit(Queue* pq);
//销毁队列
void QueueDestroy(Queue* pq);
//队尾入队列
void QueuePush(Queue* pq, QDataType x);
//队头出队列
void QueuePop(Queue* pq);
//获取队列头部元素
QDataType QueueFront(Queue* pq);
//获取队列尾部元素
QDataType QueueBack(Queue* pq);
//获取队列中有效元素个数
int QueueSize(Queue* pq);
//检测队列是否为空,如果为空返回true
bool QueueEmpty(Queue* pq);
2.3 接口函数
(1)初始化队列
队列初始化,只需要将队头链表指针和队尾链表指针置为空指针就好。
cpp
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = NULL;
pq->tail = NULL;
}
(2)销毁队列
因为是以链表为基础,需要用while循环释放掉每一个动态开辟的结点。
cpp
void QueueDestroy(Queue* pq)
{
assert(pq);
QueueNode* cur = pq->head;
while (cur != NULL)
{
QueueNode* next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
}
(3)队尾入队列
队尾入队列就是链表的尾插,但是我们有tail指针,直接在tail后面插入就好。不过需要分是否为空队列两种情况,如果是空队列,head和tail指针都指向新结点,如果不是空队列,在tail之后插入。
cpp
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
if (newnode == NULL)
{
perror("Queue malloc fail");
return;
}
newnode->data = x;
newnode->next = NULL;
if (pq->head == NULL)
{
pq->head = pq->tail = newnode;
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;
}
}
(4)队头出队列
在操作之前,我们需要判断队列是否有元素。然后在释放第一个结点,改变指针指向。
cpp
void QueuePop(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
QueueNode* next = pq->head->next;
free(pq->head);
pq->head = next;
}
(5)获取队列头部元素
cpp
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
(6)获取队列尾部元素
cpp
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
(7)获取队列中有效元素个数
这需要一个计数变量。
cpp
int QueueSize(Queue* pq)
{
assert(pq);
int n = 0;
QueueNode* cur = pq->head;
while (cur)
{
n++;
cur = cur->next;
}
return n;
}
(8)检测队列是否为空
跟栈判空相同。
cpp
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->head == NULL;
}
2.4 测试函数
写两个测试函数,第一是入队列和出队列的操作,可以一步一步调试看情况。第二个测试函数,是打印队列里的数据,现获取队头的数据,然后再出队。
cpp
void TestQueue1()
{
Queue q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
QueuePop(&q);
QueuePop(&q);
QueuePop(&q);
QueuePop(&q);
printf("%d\n", QueueBack(&q));
QueueDestroy(&q);
}
void TestQueue2()
{
Queue q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
while (!QueueEmpty(&q))
{
QDataType front = QueueFront(&q);
printf("%d ", front);
QueuePop(&q);
}
printf("\n");
}
int main()
{
TestQueue1();
TestQueue2();
return 0;
}
输出的结果:

3. 栈和队列的源代码
3.1 栈
(1)Stack.h
cpp
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int STDataType;
下面是定长的静态栈的结构,实际中一般不实用,所以我们主要实现下面的支持动态增长的栈
//typedef int STDataType;
//#define N 10
//typedef struct Stack
//{
// STDataType _a[N];
// int _top; // 栈顶
//}Stack;
// 支持动态增长的栈
typedef struct Stack
{
STDataType* a;
int top; //栈顶
int capacity; //容量
}ST;
//初始化栈
void StackInit(ST* ps);
//销毁栈
void StackDestroy(ST* ps);
//压栈
void StackPush(ST* ps, STDataType x);
//出栈
void StackPop(ST* ps);
//获取栈顶元素
STDataType StackTop(ST* ps);
//获取栈中有效元素个数
int StackSize(ST* ps);
//检测栈是否为空,为空返回true
bool StackEmpty(ST* ps);
(2)Stack.c
cpp
#include "Stack.h"
void StackInit(ST* ps)
{
assert(ps);
ps->a = NULL;
ps->top = 0;//ps->top = -1
ps->capacity = 0;
}
void StackDestroy(ST* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
void StackPush(ST* ps, STDataType x)
{
assert(ps);
if (ps->top == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
STDataType* tmp = realloc(ps->a, sizeof(STDataType) * newCapacity);
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
ps->a = tmp;
ps->capacity = newCapacity;
}
ps->a[ps->top] = x;
ps->top++;
}
void StackPop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
ps->top--;
}
STDataType StackTop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
return ps->a[ps->top - 1];
}
int StackSize(ST* ps)
{
assert(ps);
return ps->top;
}
bool StackEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;
}
(3)STtest.c
cpp
#include "Stack.h"
void TestStack1()
{
ST st;
StackInit(&st);
StackPush(&st, 1);
StackPush(&st, 2);
StackPush(&st, 3);
StackPush(&st, 4);
StackPop(&st);
StackPop(&st);
StackPop(&st);
StackPop(&st);
StackPop(&st);
StackDestroy(&st);
}
void TestStack2()
{
ST st;
StackInit(&st);
StackPush(&st, 1);
StackPush(&st, 2);
StackPush(&st, 3);
StackPush(&st, 4);
//printf("%d ", StackTop(&st));
//StackPop(&st);
//printf("%d ", StackTop(&st));
//StackPop(&st);
//StackPush(&st, 5);
//StackPush(&st, 6);
while (!StackEmpty(&st))
{
printf("%d ", StackTop(&st));
StackPop(&st);
}
StackDestroy(&st);
}
int main()
{
TestStack1();
//TestStack2();
return 0;
}
3.2 队列
(1)Queue.h
cpp
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
// 链式结构:表示队列
typedef int QDataType;
typedef struct QueueNode
{
struct QueueNode* next;
QDataType data;
}QueueNode;
// 队列的结构
typedef struct Queue
{
QueueNode* head;
QueueNode* tail;
}Queue;
//初始化队列
void QueueInit(Queue* pq);
//销毁队列
void QueueDestroy(Queue* pq);
//队尾入队列
void QueuePush(Queue* pq, QDataType x);
//队头出队列
void QueuePop(Queue* pq);
//获取队列头部元素
QDataType QueueFront(Queue* pq);
//获取队列尾部元素
QDataType QueueBack(Queue* pq);
//获取队列中有效元素个数
int QueueSize(Queue* pq);
//检测队列是否为空,如果为空返回true
bool QueueEmpty(Queue* pq);
(2)Queue.c
cpp
#include "Queue.h"
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = NULL;
pq->tail = NULL;
}
void QueueDestroy(Queue* pq)
{
assert(pq);
QueueNode* cur = pq->head;
while (cur != NULL)
{
QueueNode* next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
}
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
if (newnode == NULL)
{
perror("Queue malloc fail");
return;
}
newnode->data = 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(!QueueEmpty(pq));
QueueNode* next = pq->head->next;
free(pq->head);
pq->head = next;
}
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
int QueueSize(Queue* pq)
{
assert(pq);
int n = 0;
QueueNode* cur = pq->head;
while (cur)
{
n++;
cur = cur->next;
}
return n;
}
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->head == NULL;
}
(3)Qtest.c
cpp
void TestQueue1()
{
Queue q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
QueuePop(&q);
QueuePop(&q);
QueuePop(&q);
QueuePop(&q);
printf("%d\n", QueueBack(&q));
QueueDestroy(&q);
}
void TestQueue2()
{
Queue q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
while (!QueueEmpty(&q))
{
QDataType front = QueueFront(&q);
printf("%d ", front);
QueuePop(&q);
}
printf("\n");
}
int main()
{
//TestQueue1();
TestQueue2();
return 0;
}
总结
栈和队列的实现其实相较于链表的实现简单一些,是因为其结构的特殊要求。之后会出一篇关于栈和队列的OJ题目。多多重复,百炼成钢!
创作不易,希望这篇文章能给你带来启发和帮助,如果喜欢这篇文章,请留下你的三连,你的支持的我最大的动力!!!
