数据结构之,栈与队列
1、栈
1.1、什么是" 栈 "
栈,是一种特殊的线性表。
栈,只能在该结构固定的一端,实现数据的添加,与删除。这一端,也叫做栈顶。对应的,另一端叫做栈底。
往栈中插入数据,叫做入栈 / 压栈 / 进栈。插入数据在栈顶。如图:

删除栈中的数据,叫做出栈。删除数据也在栈顶。如图:

栈中元素的改变,遵循后进先出( Last In First Out )的原则。
1.2、栈的实现
1.2.1、栈的定义
栈的元素,可以用类似于顺序表元素的结构体,也可以用类似于链表节点的结构体。
如果用类似链表的结构:
c
struct stack
{
int data;
struct stack* next;
};
我们在之前的学习中就知道,链表头删、头插的时间复杂度为O(1)。
那么在栈中,为了降低时间复杂度,我们只能把第一个栈元素,所在的一端,当作栈顶。这样一来,时间复杂度也为O(1)。
但是,如果要插入新的栈元素,每次就要申请一个栈元素(结构体)类型大小的空间。
如果用类似顺序表的结构:
c
struct stack
{
int* arr;
int size;
int capacity;
};
结合之前所学,我们只能将数组的末端,作为栈顶,以降低时间复杂度( O(1) )。
然而,如果我们想在栈中插入数据,只需申请对应数据元素类型大小的空间。
随着要插入数据的不断增多,这种利用数组创建栈元素的方法,在申请次数和开辟空间大小上,将会比上一个结构更少。
所以,我们采用的创建栈的方法:
c
typedef int STADataType;
typedef struct stack
{
STADataType* arr;
int top; //栈顶位置
int capacity; //最大容量
}stack;
1.2.2、栈的初始化
由于栈存在空间不够要扩容,导致栈本身(连同栈中的数组一起)(的地址)改变,那么我们像操作函数中,输入的是栈的地址。
代码演示:
c
void STAInit(stack* pst)
{
assert(pst);
pst->arr = NULL;
pst->top = pst->capacity = 0;
}
1.2.3、栈的销毁
思路:
- 首先判断数组成员是否为
NULL,再释放空间 - 各项参数初始化
代码演示:
c
void STADestroy(stack* pst)
{
if (pst->arr)
free(pst->arr);
pst->arr = NULL;
pst->top = pst->capacity = 0;
}
1.2.4、入栈
已知栈顶为数组末尾。
思路:
- 判断指针有效性
- 判断是否需要扩容。如果需要:
- 重新确定最大容量
capacity:- 如果为
0,赋值一个初始值(比如4) - 如果不为0,2倍扩容
- 如果为
realloc()- 更改参数
- 重新确定最大容量
- 先赋值,后
top++。
代码实现:
c
void STAPush(stack* pst, STADataType val)
{
assert(pst);
if (pst->top == pst->capacity)
{
int newcapacity = (capacity == 0) ? 4 : 2*capacity;
STADataType* tmp = (STADataType*)realloc(newcapacity * sizeof(STADataType));
if (tmp == NULL)
{
perror("realloc failed\n");
exit(1);
}
pst = tmp;
pst->capacity = newcapacity;
}
pst->arr[pst->top++] = val;
}
1.2.5、判断栈是否为空
在出栈之前,我们有必要判断当前栈是否可以出栈,即判断栈是否为空。
判断的依据是top是否为0。这里我们借助布尔类型值,更方便理解。
代码演示:
c
#include<stdbool.h>
bool STAEmpty(stack* pst)
{
assert(pst);
return (pst->top == 0);
}
这里如果栈为空,返回ture;否则返回false。
1.2.6、出栈
思路:
- 判断栈是否为空
- 如果不为空,直接
top--就可以。这个位置还有有效值,无需担心,后续添加一个新元素就可以覆盖这个位置。
代码演示:
c
void STAPop(stack* pst)
{
assert(!STAEmpty(pst));
pst->top--;
}
1.2.7、取栈顶元素
很简单,就是返回top下标的前一个下标对应的元素:
c
STADataType STATop(stack* pst)
{
assert(!STAEmpty(pst));
return pst->arr[pst->top - 1];
}
1.2.8、获取栈中有效元素个数
就是返回top。
c
int STASize(stack* pst)
{
assert(pst);
return pst->top;
}
2、队列
提到了栈,就不得不提到队列。
2.1、什么是队列
队列,也是线性表的一种。
队列,只能在其一端插入数据,而在另一端删除数据。

插入数据的操作,为入队列,入队列的一端为队尾。
删除数据的操作,为出队列,出队列的一端为队头。
2.2、队列的实现
2.2.1、队列的定义
这时,我们又回到了类似的问题:
实现队列,是用数组呢,还是用链表呢?
从空间复杂度看,
如果我们使用数组,那么,入队列和出队列,必有一种操作,其空间复杂度为O(N)(遍历数组,挪数腾位),似乎不太理想。
而如果我们使用链表,只要我们能够实现快速找到队头节点,和队尾节点的方法,我们就能够打下空间复杂度:
c
typedef int QueueDataType;
typedef struct ListNode
{
QueueDataType data;
struct ListNode* next;
}ListNode;
typedef struct Queue
{
ListNode* phead;//定位队头节点
ListNode* ptail;//定位队尾节点
int size; //统计节点个数
}Queue;
2.2.2、队列的初始化
代码演示:
c
void QueueInit(Queue* pq)
{
assert(pq);
pq->size = 0;
pq->phead = pq->ptail = NULL;
}
2.2.3、入队列
入队列,有点像链表的尾插:
- 创建新节点
ptail后插入size++
只不过,我们还要另外判断一下,当前节点是否为空。
代码演示:
c
void QueuePush(Queue* pq, QueueDataType val)
{
assert(pq);
//创建新节点
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
if (newnode == NULL)
{
perror("malloc failed\n");
exit(1);
}
newnode->data = val;
newnode->next = NULL;
//判断节点是否为空
if (pq->phead == NULL)
{
pq->phead = pq->ptail = newnode;
}else{
pq->ptail = newnode;
pq->ptail = pq->ptail->next;
}
//节点个数记得更改
pq->size++;
}
2.2.4、判断队列是否为空
在出队列之前,有必要判断队列是否可以删除数据。
判断的依据是,phead是否为NULL,或者size是否为0:
c
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
2.2.5、出队列
phead所在位置为队首。
先存下一个节点,然后释放,再更新、phead:
c
void QueuePop(Queue* pq)
{
//判断是否可删
assert(!QueueEmpty(pq));
//判断是否只剩下一个节点
if (pq->phead == pq->ptail)
{
//此时,头尾指向同一个节点,就全部初始化
free(pq->phead);
pq->phead = pq->ptail = NULL;
}else{
//正常出队列
QueueNode* next = pq->phead->next;
free(pq->phead);
pq->phead = next;
}
//节点个数记得更改
pq->size--;
}
2.2.6、返回队首和队尾
首先也是要判断队列是否为空。
然后返回phead和ptail中对应的数据即可。
返回队首:
c
QueueDataType QueueHead(Queue* pq)
{
assert(!QueueEmpty(pq));
return pq->phead->data;
}
返回队尾:
c
QueueDataType QueueTail(Queue* pq)
{
assert(!QueueEmpty(pq));
return pq->ptail->data;
}
2.2.7、返回节点个数
这一步操作,回答了我们为什么要在队列中添加size成员。
即降低时间复杂度。
c
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
2.2.8、销毁队列
这里的队列,由一个个节点组成。那么我们就可以利用之前销毁链表的做法:
- 存放下一个节点
- 释放当前节点
- 节点向前
代码演示:
c
void QueueDestroy(Queue* pq)
{
assert(pq);
//遍历
QueueNode* pcur = pq->phead;
while (pcur)
{
QueueNode* next = pcur->next;
free(pcur);
pcur = pcur->next;
}
//出循环后,记得头尾指针及时置空
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
3、完整代码
栈:
c
//stack.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<errno.h>
#include<stdbool.h>
//定义栈
typedef int STADataType;
typedef struct stack
{
STADataType* arr;
int top; //定义栈顶的位置
int capacity; //定义最大容量
}stack;
//栈的初始化
void StackInit(stack* pst);
//栈的销毁
void StackDestroy(stack* pst);
//入栈
void StackPush(stack* pst, STADataType val);
//判断栈是否为空
bool STAEmpty(stack* pst);
//出栈
void StackPop(stack* pst);
//取栈顶元素
STADataType STATop(stack* pst);
//获取栈中有效元素个数
int STASize(stack* pst);
c
//stack.c
#include"stack.h"
//栈的初始化
void StackInit(stack* pst)
{
//判断
assert(pst);
//各个成员初始化
pst->arr = NULL;
pst->capacity = pst->top = 0;
}
//栈的销毁
void StackDestroy(stack* pst)
{
//首先判断数组成员是否为NULL,再释放
if (pst->arr)
free(pst->arr);
pst->arr = NULL;
pst->capacity = pst->top = 0;
}
//入栈
void StackPush(stack* pst, STADataType val)
{
//判断
assert(pst);
//首先判断空间够不够,不够,扩容
//扩容前,还先判断capacity是否为0
if (pst->top == pst->capacity)
{
int newcapacity = (pst->capacity == 0) ? 4 : 2 * pst->capacity;
STADataType* tmp = (STADataType*)realloc(pst->arr, newcapacity * sizeof(STADataType));
//申请失败的判断
if (tmp == NULL)
{
perror("realloc falled\n");
exit(1);
}
pst->arr = tmp;
pst->capacity = newcapacity;
}
//放数
pst->arr[pst->top++] = val;//先放数,后top++
}
//判断栈是否为空
bool STAEmpty(stack* pst)
{
assert(pst);
return pst->top == 0;
}
//出栈
void StackPop(stack* pst)
{
//判断当前栈是否可删
assert(!STAEmpty(pst));
pst->top--;
}
//取栈顶元素
STADataType STATop(stack* pst)
{
assert(pst);
return pst->arr[pst->top - 1];
}
//获取栈中有效元素个数
int STASize(stack* pst)
{
assert(pst);
return pst->top;
}
c
//test.c
#include"stack.h"
void test0()
{
stack st;
StackInit(&st);
StackPush(&st, 1);
StackPush(&st, 2);
StackPush(&st, 3);
StackPush(&st, 4);
StackPush(&st, 5);
//StackPop(&st);
//printf("%d\n", STATop(&st));
//printf("%d\n", STASize(&st));
}
int main()
{
test0();
return 0;
}
队列:
c
//Queue.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
//利用节点创建队列,就定义节点与队列
typedef int QueueDataType;
typedef struct QueueNode
{
QueueDataType data;
struct QueueNode* next;
}QueueNode;
typedef struct Queue
{
QueueNode* phead;
QueueNode* ptail;
int size;
}Queue;
//队列初始化
void QueueInit(Queue* pq);
//入队列
void QueuePush(Queue* pq, QueueDataType val);
//判断队列是否为空
bool QueueEmpty(Queue* pq);
//出队列
void QueuePop(Queue* pq);
//获取队首元素
QueueDataType QueueHead(Queue* pq);
//获取队尾元素
QueueDataType QueueTail(Queue* pq);
//返回队列有效节点个数
int QueueSize(Queue* pq);
//销毁队列
void QueueDestroy(Queue* pq);
c
//Queue.c
#include"Queue.h"
//队列初始化
void QueueInit(Queue* pq)
{
//判断
assert(pq);
//置空
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
//入队列
void QueuePush(Queue* pq, QueueDataType val)
{
//判断
assert(pq);
//创建新节点,节点在堆上申请
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
//判断是否申请失败
if (newnode == NULL)
{
perror("malloc failed\n");
exit(1);
}
newnode->data = val;
newnode->next = NULL;
//入队列之前,判断队列是否为空
if (pq->phead == NULL)
{
//为空,注意头尾和一
pq->phead = pq->ptail = newnode;
}
else {
//否则,正常入队列
pq->ptail->next = newnode;
pq->ptail = pq->ptail->next;
}
pq->size++;
}
//判断队列是否为空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->phead == NULL;
}
//出队列
void QueuePop(Queue* pq)
{
//首先判断能不能出队列
assert(!QueueEmpty(pq));
//如果能
//先判断是否只剩下一个节点
if (pq->phead == pq->ptail)
{
//直接释放,置空
free(pq->phead);
pq->phead = pq->ptail = NULL;
}else{
//先存下第一个节点的下一个节点
QueueNode* next = pq->phead->next;
//再释放
free(pq->phead);
//最后,更新第一个节点
pq->phead = next;
}
pq->size--;
}
//获取队首元素
QueueDataType QueueHead(Queue* pq)
{
//首先判断队列是否为空
assert(!QueueEmpty(pq));
//然后返回
return pq->phead->data;
}
//获取队尾元素
QueueDataType QueueTail(Queue* pq)
{
//首先判断队列是否为空
assert(!QueueEmpty(pq));
//然后返回
return pq->ptail->data;
}
//返回队列有效节点个数
int QueueSize(Queue* pq)
{
assert(pq);
//可以直接遍历,但是时间复杂度较大
//可以在队列的定义中,加入统计节点个数的变量size,每次操作过后再变化
return pq->size;
}
//销毁队列
void QueueDestroy(Queue* pq)
{
assert(pq);
//遍历,存下一个节点,释放前一个结点
QueueNode* pcur = pq->phead;
while (pcur)
{
QueueNode* next = pcur->next;
free(pcur);
pcur = next;
}
//野指针置空
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
c
//test.c
#include"Queue.h"
int main()
{
Queue queue;
QueueInit(&queue);
QueuePush(&queue, 1);
QueuePush(&queue, 2);
QueuePush(&queue, 3);
QueuePush(&queue, 4);
QueuePop(&queue);
//QueuePop(&queue);
//QueuePop(&queue);
//QueuePop(&queue);
//QueuePop(&queue);
printf("%d\n", QueueHead(&queue));
printf("%d\n", QueueTail(&queue));
printf("%d\n", QueueSize(&queue));
QueueDestroy(&queue);
return 0;
}