1.栈
1.1栈的基本概念
1.1.1概念
• 线性表(linear list)是相同类型的n(n>=0)个数据元素的有限序列,若⽤L命名为线性表,则⼀般⽰
为 L= (a1 , a2 , ..., a**i, a**i+1 , ..., a**n)
• 栈(stack)是限定仅在⼀端进⾏插⼊或删除操作的线性表。因此,对栈来说进⾏插⼊删除数据的⼀
端称为栈顶(top),另⼀端称为栈底(bottom),不含元素的空表称为空栈。栈中的数据元素遵循后
进先出(LIFO Last In First Out)的特性。
• 栈插⼊和删除数据都在栈顶端,插⼊数据⼀般叫做⼊栈/进栈,删除数据⼀般叫做出栈。

• 现实中⼿枪弹夹和⽻⽑球桶就是⼀个栈结构。

1.1.2⼊栈出栈顺序
• 假设⼊栈顺序为a1,a2,a3,a4,a5,那么出栈顺序是什么呢?⼤家最容易想到的是所有数据都⼊以
后再出栈就是a5,a4,a3,a2,a1;实际上有多种出栈顺序,⽐如每个数据⼊栈以后⻢上出栈,所以另
⼀种出栈顺序也可以是a1,a2,a3,a4,a5;还可以是a1,a2,a3⼊栈,出a3,a2,再⼊栈a4,a5,再出栈
a5,a4,a1,也就是说出栈顺序为a3,a2,a5,a4,a1; 通过分析我们可以看到因为出栈时机的不同,可以
有很多种出栈顺序。

1.1.3栈的基本操作
• 栈是⼀种只允许在⼀端进⾏插⼊和删除的线性表,栈插⼊和删除数据都在栈顶端,插⼊数据⼀叫
做⼊栈,删除数据⼀般叫做出栈,所栈最核⼼的接⼝函数就是⼊栈和出栈函数。
//栈的初始化
void StackInit(Stack& s);
*//栈的销毁
void StackDestroy(Stack& s);
*// x元素⼊栈*(进栈)*
void StackPush(Stack& s, STDataType x);
*//*将栈顶元素出栈,并⽤返回栈顶元素
STDataType StackPop(Stack& s);
*//*获取栈顶元素并返回
STDataType StackTop(Stack& s);
*//*获取栈中有效元素个数
int StackSize(Stack& s);
*//*检测栈是否为空,如果是空返回真,否则返回假
bool StackEmpty(Stack& s);
1.2栈的顺序存储
栈是只允许在⼀端进⾏元素插⼊和删除操作的特殊线性表。⽽线性表有顺序和链式两种存储结构故
栈也有顺序和链式两种存储结构。
1.2.1静态顺序栈和动态顺序栈结构
| 静态栈结构定义 | 动态栈结构定义 |
|---|---|
![]() |
![]() |
1.2.2动态顺序栈实现
• 需要注意的是栈结构中初始化时, top=-1 还是 top=0 , top=-1 代表top指向栈顶元素,那么⼊栈时要先++top,再把⼊栈数据放到top位置。 top=0 代表top指向栈顶元素的下⼀个位置, 那么⼊栈时要先把⼊栈数据放到top位置,再++top。其他接⼝函数处理细节也要对应调整。这两种⽅式没有优劣之分。

cpp
#pragma once
//stack.h
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int STDataType;
typedef struct {
STDataType* arr; // 指向栈数组空间的指针
int top; // 栈顶位置
int capacity; // 容量
}Stack;
// 栈的初始化
void StackInit(Stack* s);
// 栈的销毁
void StackDestroy(Stack* s);
// x元素⼊栈(进栈)
void StackPush(Stack* s, STDataType x);
// 将栈顶元素出栈,并⽤返回栈顶元素
STDataType StackPop(Stack* s);
// 获取栈顶元素并返回
STDataType StackTop(Stack* s);
// 获取栈中有效元素个数
int StackSize(Stack* s);
// 检测栈是否为空,如果是空返回真,否则返回假
bool StackEmpty(Stack* s);
//stack.c
#include"stack.h"
void StackInit(Stack* s) {
assert(s);
s->arr = (STDataType*)malloc(4 * sizeof(STDataType));
if (NULL == s->arr) {
printf("StackInit: 申请空间失败!!!\n");
exit(-1);
}
// 初始化时,top = 0,表⽰top指向的是栈顶元素的下⼀个位置
// 初始化时,top = -1,表⽰top指向的是栈顶元素
// 两种⽅式都可以,选择哪⼀种后⾯的逻辑都对应调整即可
s->top = 0;
s->capacity = 4;
}
void StackDestroy(Stack* s) {
assert(s);
if (s->arr) {
free(s->arr);
s->arr = NULL;
s->top = 0;
s->capacity = 0;
}
}
// x元素⼊栈(进栈)
void StackPush(Stack* s, STDataType x) {
assert(s);
// 满了
if (s->top == s->capacity) {
STDataType* tmp = (STDataType*)realloc(s->arr, sizeof(STDataType) * s -> capacity * 2);
if (tmp == NULL) {
printf("StackPush: 扩容空间失败!!!\n");
exit(-1);
}
s->arr = tmp;
s->capacity *= 2;
}
s->arr[s->top] = x;
s->top++;
}
// 将栈顶元素出栈,并⽤返回栈顶元素
STDataType StackPop(Stack* s) {
assert(s);
assert(!StackEmpty(s));
STDataType topE = s->arr[s->top - 1];
--s->top;
return topE;
}
// 获取栈顶元素并返回
STDataType StackTop(Stack* s) {
assert(s);
assert(!StackEmpty(s));
return s->arr[s->top - 1];
}
// 获取栈中有效元素个数
int StackSize(Stack* s) {
assert(s);
return s->top;
}
// 检测栈是否为空,如果是空返回真,否则返回假
bool StackEmpty(Stack* s) {
assert(s);
return s->top == 0;
}
#include "Stack.h"
int main() {
Stack s;
StackInit(&s);
// ⼊栈,检测栈顶数据元素 和 栈中有效数据元素是否正确
StackPush(&s, 1);
StackPush(&s, 2);
StackPush(&s, 3);
StackPush(&s, 4);
StackPush(&s, 5);
//printf("栈中元素总数为: %d\n", StackSize(&s));
printf("栈顶元素为: %d\n", StackTop(&s));
// 出栈
printf("出栈:栈顶元素为: %d\n", StackPop(&s));
printf("出栈:栈顶元素为: %d\n", StackPop(&s));
printf("出栈:栈顶元素为: %d\n", StackPop(&s));
printf("出栈:栈顶元素为: %d\n", StackPop(&s));
printf("出栈:栈顶元素为: %d\n", StackPop(&s));
// 断⾔报错
//printf("出栈:栈顶元素为: %d\n", StackPop(&s));
//printf("栈顶元素为: %d\n", StackTop(&s));
StackPush(&s, 6);
StackPush(&s, 7);
// 出栈
printf("出栈:栈顶元素为: %d\n", StackPop(&s));
printf("出栈:栈顶元素为: %d\n", StackPop(&s));
StackDestroy(&s);
return 0;
}
1.3栈的链式存储
• 栈的链式存储,我们可以选⽤单链表结构,也可以选⽤双向链表结构。但是需要注意的是,如果⽤单链表必需⽤链表头这⼀端做栈顶,⼊栈出栈就是对应头插头删,时间复杂度为O(1) 。否则⽤链表尾做栈顶,出栈对应尾删代价太⼤了,时间复杂度为 O(N)。
•
如果选⽤双向链表,则⽤表头表尾做栈顶都可以,因为双向链表头尾插⼊删除效率都是 O(1。当
然我们完全没必要选择双向链表,因为单链表就可以⾼效实现,还省空间⼀些。双向链表没有优
势,每个结点还要多存储⼀个前驱指针,使⽤它纯粹浪费了。
• 其次我们选择单链表实现,可以带头结点,也可以不带头结点;但是这⾥⼊栈出栈就是对应头头
删,单链表带头结点时,头插头删并不会带来什么便利,所以⼀般我们选择不带头结点实现即可。

cpp
#pragma once
// LinkStack.h
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
// 链式栈中存储的数据元素类型
typedef int STDataType;
// 链式栈底层单链表中结点的定义
typedef struct LinkStackNode {
struct LinkStackNode* next;
STDataType data;
}LSNode;
// 链式栈结构的定义
typedef struct {
LSNode* topHead; // 栈顶结点指针
int size; // 栈中数据个数
}LinkStack;
// 初始化链式栈s
void LinkStackInit(LinkStack* s);
// 销毁链式栈s
void LinkStackDestroy(LinkStack* s);
// x⼊栈
void LinkStackPush(LinkStack* s, STDataType x);
// 出栈,并返回栈顶元素
STDataType LinkStackPop(LinkStack* s);
// 获取栈顶元素
STDataType LinkStackTop(LinkStack* s);
// 获取栈中有效元素个数
int LinkStackSize(LinkStack* s);
// 检测栈是否为空,如果是空返回真,否则返回假
bool LinkStackEmpty(LinkStack* s);
//LinkStack.c
#include"LinkStack.h"
void LinkStackInit(LinkStack* s) {
assert(s);
// 这⾥可以使⽤带头结点的单链表,也可以使⽤不带头结点
// 相对⽽⾔不带头结点更推荐⼀些,因为⼊栈出栈主要是头插头删
// 头结点价值不⼤
s->topHead = NULL;
s->size = 0;
}
void LinkStackDestroy(LinkStack* s) {
assert(s);
LSNode* cur = s->topHead;
while (cur) {
LSNode* next = cur->next;
free(cur);
cur = next;
}
s->topHead = NULL;
s->size = 0;
}
// 获取栈中有效元素个数
int LinkStackSize(LinkStack* s) {
assert(s);
return s->size;
}
// 检测栈是否为空,如果是空返回真,否则返回假
bool LinkStackEmpty(LinkStack* s) {
assert(s);
return s->topHead == NULL;
}
// x⼊栈
void LinkStackPush(LinkStack* s, STDataType x) {
assert(s);
// 1. 申请新结点
LSNode* newNode = (LSNode*)malloc(sizeof(LSNode));
if (NULL == newNode) {
printf("LinkStackPush:申请节点失败");
exit(-1);
}
newNode->data = x;
newNode->next = s->topHead;
s->topHead = newNode;
s->size++;
}
// 出栈,并返回栈顶元素
// 出栈,并返回栈顶元素
STDataType LinkStackPop(LinkStack* s) {
assert(s);
assert(!LinkStackEmpty(s));
STDataType top = s->topHead->data;
LSNode* delNode = s->topHead;
s->topHead = delNode->next;
free(delNode);
s->size--;
return top;
}
// 获取栈顶元素
STDataType LinkStackTop(LinkStack* s) {
assert(s);
assert(!LinkStackEmpty(s));
return s->topHead->data;
}
int main() {
LinkStack s;
LinkStackInit(&s);
// ⼊栈,检测栈顶数据元素 和 栈中有效数据元素是否正确
LinkStackPush(&s, 1);
LinkStackPush(&s, 2);
LinkStackPush(&s, 3);
printf("出栈:栈顶元素为: %d\n", LinkStackPop(&s));
printf("出栈:栈顶元素为: %d\n", LinkStackPop(&s));
LinkStackPush(&s, 4);
LinkStackPush(&s, 5);
printf("栈中元素总数为: %d\n", LinkStackSize(&s));
printf("栈顶元素为: %d\n", LinkStackTop(&s));
// 出栈
printf("出栈:栈顶元素为: %d\n", LinkStackPop(&s));
printf("出栈:栈顶元素为: %d\n", LinkStackPop(&s));
printf("出栈:栈顶元素为: %d\n", LinkStackPop(&s));
//printf("出栈:栈顶元素为: %d\n", LinkStackPop(&s));
//printf("出栈:栈顶元素为: %d\n", LinkStackPop(&s));
// 断⾔报错
//printf("出栈:栈顶元素为: %d\n", LinkStackPop(&s));
LinkStackDestroy(&s);
return 0;
}
1.4栈的顺序存储和链式存储对比
• 栈的顺序存储和链式存储都可以做到⼊栈和出栈时间复杂度为 O(1) ,效率都很⾼。
• 栈的顺序存储的唯⼀问题是⼊栈时空间不够,扩容有⼀定的消耗,但是这个消耗⼏乎可以忽略不计,因为插⼊⼀定量数据才会扩容。
• 栈的顺序存储相⽐链式存储CPU⾼速缓存命中率较⾼,且没有内存碎⽚。所以栈的实现⼀般使
⽤顺序存储。
2.队列
2.1队列的基本概念
2.1.1概念
• 线性表(linear list)是相同类型的n(n>=0)个数据元素的有限序列,若⽤L命名为线性表,则⼀般⽰
为 L= (a1 , a2 , ..., a**i, a**i+1 , ..., a**n)
• 队列(queue)是限定在⼀端插⼊另⼀端删除的线性表。因此,对队列来说,插⼊那⼀端叫做队尾, 插⼊叫⼊队,删除那⼀端叫队头,删除叫做出队。队列中的数据元素遵循先进先出(FIFO First In
First Out)的特性。
• 队列的⼊队出队顺序就简单多了,⽆论如何操作,⼀种⼊队顺序只对应⼀种出队顺序。

2.1.2队列的基本操作
• 队列在队尾⼊数据,在对头出数据,主要⽀持的核⼼接⼝就是⼊队和出队。
*//*初始化队列
void QueueInit(LinkQueue* q);
*//*销毁队列
void QueueDestroy(LinkQueue* q);
*//*检测队列是否为空,空返回真,否则返回假
bool QueueEmpty(LinkQueue* q);
*//*获取队列中有效元素个数
int QueueSize(LinkQueue* q);
//获取队头元素
QDataType QueueFront(LinkQueue* q);
//将x⼊队列
void EnQueue(LinkQueue* q, QDataType x);
*//*出队并返回队头数据
QDataType DeQueue(LinkQueue* q);
2.2队列的链式存储
2.2.1链式队列的结构
• 队列的链式存储,我们可以选⽤单链表结构,也可以选⽤双向链表结构。他们⼊队对应着在表插
⼊,出队对应着在表头删除。当然我们完全没必要选择双向链表,因为单链表就可以⾼效实现,还
省空间⼀些。双向链表没有优势,每个结点还要多存储⼀个前驱指针,使⽤它纯粹浪费了。
• 其次这⾥我们选择单链表实现,⼊队就是尾插,出队就是头删。可以带头结点,也可以不带头结
点,带头结点的结构略简单⼀点

cpp
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int QDataType; // 队列中存储数据元素的类型
// 采⽤单链表实现队列,链表结点的定义
typedef struct LinkNode {
QDataType data;
struct LinkNode* next;
}QNode;
typedef struct {
QNode* front;
QNode* rear;
int size;
}LinkQueue;
// 初始化队列
void QueueInit(LinkQueue* q);
// 销毁队列
void QueueDestroy(LinkQueue* q);
// 检测队列是否为空,空返回真,否则返回假
bool QueueEmpty(LinkQueue* q);
// 获取队列中有效元素个数
int QueueSize(LinkQueue* q);
// 获取队头元素
QDataType QueueFront(LinkQueue* q);
// 将x⼊队列
void EnQueue(LinkQueue* q, QDataType x);
// 出队并返回队头数据
QDataType DeQueue(LinkQueue* q);
//LinkQueue.c
#include"LinkQueue.h"
// 初始化队列
void QueueInit(LinkQueue* q) {
assert(q);
// 这⾥可以实现为带头结点,也可以不带头结点,我们这⾥选择带头结点的结构
q->front = q->rear = (QNode*)malloc(sizeof(QNode));
if (q->front == NULL) {
printf("QueueInit: 申请结点失败\n");
exit(-1);
}
q->front->next = NULL;
q->size = 0;
}
// 销毁队列
void QueueDestroy(LinkQueue* q) {
assert(q);
QNode* cur = q->front;
while (cur) {
QNode* next = cur->next;
free(cur);
cur = next;
}
q->front = q->rear = NULL;
q->size = 0;
}
// 检测队列是否为空,空返回真,否则返回假
bool QueueEmpty(LinkQueue* q) {
assert(q);
return q->size == 0;
}
// 获取队列中有效元素个数
int QueueSize(LinkQueue* q) {
assert(q);
return q->size;
}
// 获取队头元素
QDataType QueueFront(LinkQueue* q) {
assert(q);
assert(!QueueEmpty(q));
return q->front->next->data;
}
// 将x⼊队列
void EnQueue(LinkQueue* q, QDataType x) {
assert(q);
QNode* newNode = (QNode*)malloc(sizeof(QNode));
if (newNode == NULL) {
printf("EnQueue: 申请结点失败\n");
exit(-1);
}
newNode->data = x;
newNode->next = NULL;
q->rear->next = newNode;
q->rear = newNode;
q->size++;
}
// 出队并返回队头数据
QDataType DeQueue(LinkQueue* q) {
assert(q);
assert(!QueueEmpty(q));
QNode* delNode = q->front->next;
QDataType x = delNode->data;
// 删除第⼀个结点
q->front->next = delNode->next;
free(delNode);
delNode = NULL;
q->size--;
// 最后⼀个结点被删掉,rear要指向哨兵位的头结点
if (q->front->next == NULL) {
q->rear = q->front;
}
return x;
}
// test.c
#include "LinkQueue.h"
int main() {
LinkQueue lq;
QueueInit(&lq);
EnQueue(&lq, 1);
EnQueue(&lq, 2);
EnQueue(&lq, 3);
EnQueue(&lq, 4);
printf("队列数据个数%d\n", QueueSize(&lq));
printf("出队%d\n", DeQueue(&lq));
printf("出队%d\n", DeQueue(&lq));
EnQueue(&lq, 5);
EnQueue(&lq, 6);
printf("出队%d\n", DeQueue(&lq));
printf("出队%d\n", DeQueue(&lq));
printf("队列数据个数%d\n", QueueSize(&lq));
printf("出队%d\n", DeQueue(&lq));
printf("出队%d\n", DeQueue(&lq));
//printf("出队%d\n", DeQueue(&lq));
QueueDestroy(&lq);
return 0;
}
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int QDataType; // 队列中存储数据元素的类型
// 采⽤单链表实现队列,链表结点的定义
typedef struct LinkNode {
QDataType data;
struct LinkNode* next;
}QNode;
typedef struct {
QNode* front;
QNode* rear;
int size;
}LinkQueue;
// 初始化队列
void QueueInit(LinkQueue* q);
// 销毁队列
void QueueDestroy(LinkQueue* q);
// 检测队列是否为空,空返回真,否则返回假
bool QueueEmpty(LinkQueue* q);
// 获取队列中有效元素个数
int QueueSize(LinkQueue* q);
// 获取队头元素
QDataType QueueFront(LinkQueue* q);
// 将x⼊队列
void EnQueue(LinkQueue* q, QDataType x);
// 出队并返回队头数据
QDataType DeQueue(LinkQueue* q);
#include"LinkQueue.h"
// 初始化队列
void QueueInit(LinkQueue* q) {
assert(q);
// 这⾥可以实现为带头结点,也可以不带头结点,我们这⾥选择带头结点的结构
q->front = q->rear = (QNode*)malloc(sizeof(QNode));
if (q->front == NULL) {
printf("QueueInit: 申请结点失败\n");
exit(-1);
}
q->front->next = NULL;
q->size = 0;
}
// 销毁队列
void QueueDestroy(LinkQueue* q) {
assert(q);
QNode* cur = q->front;
while (cur) {
QNode* next = cur->next;
free(cur);
cur = next;
}
q->front = q->rear = NULL;
q->size = 0;
}
// 检测队列是否为空,空返回真,否则返回假
bool QueueEmpty(LinkQueue* q) {
assert(q);
return q->size == 0;
}
// 获取队列中有效元素个数
int QueueSize(LinkQueue* q) {
assert(q);
return q->size;
}
// 获取队头元素
QDataType QueueFront(LinkQueue* q) {
assert(q);
assert(!QueueEmpty(q));
return q->front->next->data;
}
// 将x⼊队列
void EnQueue(LinkQueue* q, QDataType x) {
assert(q);
QNode* newNode = (QNode*)malloc(sizeof(QNode));
if (newNode == NULL) {
printf("EnQueue: 申请结点失败\n");
exit(-1);
}
newNode->data = x;
newNode->next = NULL;
q->rear->next = newNode;
q->rear = newNode;
q->size++;
}
// 出队并返回队头数据
QDataType DeQueue(LinkQueue* q) {
assert(q);
assert(!QueueEmpty(q));
QNode* delNode = q->front->next;
QDataType x = delNode->data;
// 删除第⼀个结点
q->front->next = delNode->next;
free(delNode);
delNode = NULL;
q->size--;
// 最后⼀个结点被删掉,rear要指向哨兵位的头结点
if (q->front->next == NULL) {
q->rear = q->front;
}
return x;
}
// test.c
#include "LinkQueue.h"
int main() {
LinkQueue lq;
QueueInit(&lq);
EnQueue(&lq, 1);
EnQueue(&lq, 2);
EnQueue(&lq, 3);
EnQueue(&lq, 4);
printf("队列数据个数%d\n", QueueSize(&lq));
printf("出队%d\n", DeQueue(&lq));
printf("出队%d\n", DeQueue(&lq));
EnQueue(&lq, 5);
EnQueue(&lq, 6);
printf("出队%d\n", DeQueue(&lq));
printf("出队%d\n", DeQueue(&lq));
printf("队列数据个数%d\n", QueueSize(&lq));
printf("出队%d\n", DeQueue(&lq));
printf("出队%d\n", DeQueue(&lq));
//printf("出队%d\n", DeQueue(&lq));
QueueDestroy(&lq);
return 0;
}

