目录
一、栈(顺序表)
1、栈的概念及结构
栈:是一种线性表,只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端 称为栈顶,另一端称为栈底。
- 压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
- 出栈:栈的删除操作叫做出栈。出数据也在栈顶。
下图所示可以更好的观察怎么入栈,怎么出栈。
特点:后进先出。
2、栈的实现
因为栈是一种线性表,所以存储结构可以是顺序表或链表两种形式,相对于顺序表这种结构实现会更优一些。顺序表在尾上插入数据的代价较小。
注意:顺序表就要考虑到这个栈是否会满,如果栈满,就需要扩容,判断的标准为top == capacity.
2.1 栈的结构体
typedef int STDataType;
typedef struct Stack
{
STDataType* a;//数组
int capacity; //栈的容量
int top; //初始为0,表示栈顶位置下一个位置的下标
}ST;
2.2 栈的初始化
为了下面的操作方便,我在这里直接先申请空间,然后栈满时,再去扩容
// 初始化栈
void StackInit(ST* ps)
{
assert(ps);
ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
if (ps->a == NULL)
{
perror("malloc fail");
exit(-1);
}
ps->top = 0;
ps->capacity = 4;
}
2.3 入栈
注意:入栈之前首先要判断一下,栈是否已满,栈满时,需要我们扩容,方可继续入栈。
当top == capacity时,说明栈满,此时,我们2倍的扩容,防止一次申请过多空间,导致空间浪费。注意要将capacity扩大2倍,否则容量空间申请出来,但是大小没变。
//入栈
void StackPush(ST* ps, STDataType x)
{
assert(ps);
//扩容
if (ps->capacity == ps->top)
{
STDataType* tmp = (STDataType*)realloc(ps->a, ps->capacity * 2 * sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
ps->a = tmp;
ps->capacity *= 2;//注意要将capacity扩大2倍
}
ps->a[ps->top] = x;
ps->top++;
}
2.4 出栈
出栈我们首先要暴力检测一波栈是否为空,为空时,就不能出栈。直接将top--就可以。
//出栈
void StackPop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
ps->top--;
}
2.5 获取栈顶数据
注意:刚开始设置top时,它的位置就是栈顶位置的下一个位置。所以去栈顶数据时,要top-1。
//获取栈顶元素
STDataType StackTop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
return ps->a[ps->top - 1];
}
2.6 栈中有效数据
//获取栈中有效元素个数
int StackSize(ST* ps)
{
assert(ps);
return ps->top;
}
2.7 判断栈空
判断栈是否为空,就是看top是否为0。top=0时,就表示为空。
bool StackEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;
}
2.8 栈的销毁
销毁栈时,我们要先将申请的空间释放掉,最好将其置空。
//销毁
void StackDestory(ST* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->capacity = ps->top = 0;
}
3、源代码
3.1 Stack.h
#pragma once
#include <stdbool.h>
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef int STDataType;
typedef struct Stack
{
STDataType* a;//数组
int capacity;
int top; //初始为0,表示栈顶位置下一个位置的下标
}ST;
// 初始化栈
void StackInit(ST* ps);
//销毁
void StackDestory(ST* ps);
//入栈
void StackPush(ST* ps, STDataType x);
//出栈
void StackPop(ST* ps);
//获取栈顶元素
STDataType StackTop(ST* ps);
//获取栈中有效元素个数
int StackSize(ST* ps);
bool StackEmpty(ST* ps);
3.2 Stack.c
#include "Stack.h"
// 初始化栈
void StackInit(ST* ps)
{
assert(ps);
ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
if (ps->a == NULL)
{
perror("malloc fail");
exit(-1);
}
ps->top = 0;
ps->capacity = 4;
}
//销毁
void StackDestory(ST* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->capacity = ps->top = 0;
}
//入栈
void StackPush(ST* ps, STDataType x)
{
assert(ps);
//扩容
if (ps->capacity == ps->top)
{
STDataType* tmp = (STDataType*)realloc(ps->a, ps->capacity * 2 * sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
ps->a = tmp;
ps->capacity *= 2;
}
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.3 test.c
#include "Stack.h"
void TestStack1()
{
ST st;
StackInit(&st);
StackPush(&st, 1);
StackPush(&st, 2);
StackPush(&st, 3);
StackPush(&st, 4);
StackPush(&st, 5);
StackPop(&st);
StackPop(&st);
StackPop(&st);
printf("%d\n", StackTop(&st));
printf("size:%d\n", StackSize(&st));
//printf("size:%d\n", st.top);
StackDestory(&st);
}
int main()
{
TestStack1();
return 0;
}
二、队列(单链表)
1、队列的基本概念及结构
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾出队列:进行删除操作的一端称为队头。
下图所示可以更好的观察怎么入队列,怎么出队列。
特点:先进先出。
2、队列的实现
队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。
2.1 队列的结构体
- 第一个结构体表示队列结点
- 第二个结构体表示队列,里面包含队头和队尾结点,还有队列的大小。
typedef int QDataType;
// 链式结构:表示队列
typedef struct QueueNode
{
QDataType data;
struct QueueNode* next;
}QNode;
// 队列的结构
typedef struct Queue
{
QNode* head; //队头
QNode* tail; //队尾
int size; // 队列的大小
}Queue;
2.2 队列初始化
我们将队列进行初始化,是要用 head 和 tail 两个指针来控制函数,所以函数的参数是Queue*。
// 初始化队列
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = pq->tail = NULL;
pq->size = 0;
}
2.3 入队列
入队列是在尾部插入的。首先是要申请一个结点,然后再进行尾插,尾插分两种情况:(1)如果队列为空时,head = tail = newnode (2)如果队列不为空时,直接将其尾插。最后将size++即可。
// 队尾入队列
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
if (pq->tail == NULL)
{
pq->head = pq->tail = newnode;
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;
}
pq->size++;
}
2.4 出队列
出队列是在对头出的,需要注意的是:首先要保证队列有数据才可以出队列,所以我们要判断队列是否为空,如果不为空,此时,还有两种情况:(1)只有一个结点时,就相当于只有一个头结点,直接将其释放,然后置空。(2)如果有多个数据时,需要先保存头结点(如果不保存头结点,后面的链表就链接不上),然后将其释放,再置空。
// 队头出队列
void QueuePop(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
if (pq->head->next == NULL)//只有一个结点
{
free(pq->head);
pq->head = pq->tail = NULL;
}
else
{
QNode* del = pq->head;
pq->head = pq->head->next;
free(del);
}
pq->size--;
}
2.5 获取队头元素
// 获取队列头部元素
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
2.6 获取队尾元素
// 获取队列队尾元素
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
2.7 队列中有效数据
我们在设置队列的结构体时,已经设置有 size ,所以队列中的有效数据,其实就是 size 的大小。
// 获取队列中有效元素个数
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
2.8 队列是否为空
当 head 和 tail 指针为空时,就说明此时队列为空。
// 检测队列是否为空,如果为空返回非零,非空返回0
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->head == NULL && pq->tail == NULL;
}
2.9 销毁队列
队列的销毁和栈的销毁还是有区别的,因为栈是用的顺序表结构,它的存储空间是连续的,所以释放是可以一次性全部释放的,而队列是用的链表结构,它的存储空间不是连续的,所以释放空间的时候需要一个一个释放,下面用的是循环方式将每个节点释放掉,最后再将其置空。
// 销毁队列
void QueueDestroy(Queue* pq)
{
assert(pq);
QNode* cur = pq->head;
while (cur)
{
QNode* del = cur;
cur = cur->next;
free(del);
}
pq->head = pq->tail = NULL;
pq->size = 0;
}
3、源代码
3.1 Queue.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int QDataType;
// 链式结构:表示队列
typedef struct QueueNode
{
QDataType data;
struct QueueNode* next;
}QNode;
// 队列的结构
typedef struct Queue
{
QNode* head;
QNode* tail;
int size;
}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);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
bool QueueEmpty(Queue* pq);
3.2 Queue.c
#include "Queue.h"
// 初始化队列
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = pq->tail = NULL;
pq->size = 0;
}
// 销毁队列
void QueueDestroy(Queue* pq)
{
assert(pq);
QNode* cur = pq->head;
while (cur)
{
QNode* del = cur;
cur = cur->next;
free(del);
//del = NULL;
}
pq->head = pq->tail = NULL;
pq->size = 0;
}
// 队尾入队列
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
if (pq->tail == NULL)
{
pq->head = pq->tail = newnode;
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;
}
pq->size++;
}
// 队头出队列
void QueuePop(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
if (pq->head->next == NULL)//只有一个结点
{
free(pq->head);
pq->head = pq->tail = NULL;
}
else
{
QNode* del = pq->head;
pq->head = pq->head->next;
free(del);
}
pq->size--;
}
// 获取队列头部元素
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)
{
int size = 0;
return pq->size;
}
// 检测队列是否为空,如果为空返回非零,非空返回0
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->head == NULL && pq->tail == NULL;
}
3.3 test.c
#include "Queue.h"
void TestQueue1()
{
Queue q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
printf("%d\n", QueueSize(&q));
printf("%d\n", QueueEmpty(&q));//ǿշ0
printf("%d\n", QueueFront(&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))
{
printf("%d ", QueueFront(&q));
QueuePop(&q);
}
printf("\n");
printf("%d\n", QueueSize(&q));
printf("%d\n", QueueEmpty(&q));//շطֵ
}
int main()
{
//TestQueue1();
TestQueue2();
return 0;
}
三、循环队列
我们有时还会使用一种队列叫循环队列。如操作系统课程讲解生产者消费者模型时可以就会使用循环队列。环形队列可以使用数组实现,也可以使用循环链表实现。
队满条件:(rear + 1) % Maxsize = front;
队空条件:front = rear;
队列元素个数:(rear + Maxsize - front) % Maxsize;
本文要是有不足的地方,欢迎大家在下面评论,我会在第一时间更正。
老铁们,记着点赞加关注!!!