【数据结构】栈和队列
1.栈
栈的概念
栈 :一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈。(入数据在栈顶)
出栈:栈的删除操作叫做出栈。(出数据也在栈顶)
栈的实现
栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的 代价比较小。
初始化栈
首先,我们需要用结构体创建一个栈,这个结构体需要包括栈的基本内容(栈,栈顶,栈的容量)。
cpp
typedef int DataType;
// 动态栈的结构
typedef struct Stack {
DataType *a;
int top; // 栈顶
int capacity;// 容量
} Stack;
然后,我们需要一个初始化函数,对刚创建的栈进行初始化。
cpp
// 初始化
void StackInit(Stack *ps) {
assert(ps);
// 初始化的时候给数组一段空间
ps->a = (DataType *) malloc(sizeof(DataType) * 4);
if (ps->a == NULL) {
perror("malloc fail");
exit(-1);
}
ps->top = -1; // 栈顶初始化为-1
ps->capacity = 4;// 容量为4
}
销毁栈
因为栈的内存空间是动态开辟出来的,当我们使用完后必须释放其内存空间,避免内存泄漏。
cpp
// 销毁栈
void StackDestroy(Stack *ps) {
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = -1;
ps->capacity = 0;
}
入栈
进行入栈操作前,我们需要检测栈的当前状态,若已满,则需要先对其进行增容,然后才能进行入栈操作。
cpp
// Push
void StackPush(Stack *ps, DataType x) {
assert(ps);
// 扩容
if (ps->top + 1 == ps->capacity) {
DataType *tmp = (DataType *) realloc(ps->a, ps->capacity * 2 * sizeof(DataType));
if (tmp == NULL) {
perror("realloc fail");
exit(-1);
}
ps->a = tmp;
ps->capacity *= 2;
}
// 不需要扩容则Push,top初始化为-1 ,先++ 在插入
ps->top++;
ps->a[ps->top] = x;
}
出栈
出栈操作比较简单,即让栈顶的位置向下移动一位即可。但需检测栈是否为空,若为空,则不能进行出栈操作。
cpp
// Pop
void StackPop(Stack *ps) {
assert(ps); // 地址不为空
assert(!StackEmpty(ps));// 栈不为空
ps->top--;
}
获取栈顶元素
获取栈顶元素,即获取栈的最上方的元素。若栈为空,则不能获取。
cpp
// 取栈顶
DataType StackTop(Stack *ps) {
assert(ps);
return ps->a[ps->top];
}
检测栈是否为空
检测栈是否为空,即判断栈顶的位置是否是-1即可。若栈顶是-1,则栈为空。
cpp
// 判断是否为空
bool StackEmpty(Stack *ps) {
assert(ps);
// 如果top是-1 则为空 返回真
return ps->top == -1;
}
获取栈中有效元素个数
因为top记录的是栈顶,使用top的值便代表栈中有效元素的个数。
cpp
// 求栈的长度
size_t StackSize(Stack *ps) {
assert(ps);
return ps->top + 1;
}
完整代码
cpp
#pragma once
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
typedef int DataType;
// 动态栈的结构
typedef struct Stack {
DataType *a;
int top; // 栈顶
int capacity;// 容量
} Stack;
// 初始化
void StackInit(Stack *ps) {
assert(ps);
// 初始化的时候给数组一段空间
ps->a = (DataType *) malloc(sizeof(DataType) * 4);
if (ps->a == NULL) {
perror("malloc fail");
exit(-1);
}
ps->top = -1; // 栈顶初始化为-1
ps->capacity = 4;// 容量为4
}
// 销毁栈
void StackDestroy(Stack *ps) {
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = -1;
ps->capacity = 0;
}
// 判断是否为空
bool StackEmpty(Stack *ps) {
assert(ps);
// 如果top是-1 则为空 返回真
return ps->top == -1;
}
// Push
void StackPush(Stack *ps, DataType x) {
assert(ps);
// 扩容
if (ps->top + 1 == ps->capacity) {
DataType *tmp = (DataType *) realloc(ps->a, ps->capacity * 2 * sizeof(DataType));
if (tmp == NULL) {
perror("realloc fail");
exit(-1);
}
ps->a = tmp;
ps->capacity *= 2;
}
// 不需要扩容则Push,top初始化为-1 ,先++ 在插入
ps->top++;
ps->a[ps->top] = x;
}
// Pop
void StackPop(Stack *ps) {
assert(ps); // 地址不为空
assert(!StackEmpty(ps));// 栈不为空
ps->top--;
}
// 取栈顶
DataType StackTop(Stack *ps) {
assert(ps);
return ps->a[ps->top];
}
// 求栈的长度
size_t StackSize(Stack *ps) {
assert(ps);
return ps->top + 1;
}
2.队列
队列的概念
队列 :只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表。队列遵守先进先出FIFO(First In First Out)的原则。
入队列:队列的插入操作叫做入队列,进行插入操作的一端称为队尾。
出队列:队列的删除操作叫做出队列,进行删除操作的一端称为队头。
队列的实现
队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数 组头上出数据,效率会比较低。
初始化队列
首先我们需要创建一个结点类型,类型包含了该结点的数据和指向下一结点的指针。
cpp
typedef int QDataType;
// 队列里队头和队尾的结构
typedef struct QueueNode {
QDataType data; //数据域
struct QueueNode *next;//指针域
} QNode;
队列与普通链表又有所不同,普通链表只需要知道链表的头指针,而队列的信息包括了队头和队尾,所以我们需要再创建一个结构体用于存放队列的队头和队尾。并且记录队列的长度。
cpp
// 队列的结构
typedef struct Queue {
QNode *front;// 队头
QNode *rear; // 队尾
size_t size; // 队列长度
} Queue;
然后,我们需要一个初始化函数,对刚创建的队列进行初始化。
cpp
// 队列的初始化
void QueueInit(Queue *pq) {
assert(pq);
pq->front = NULL;
pq->rear = NULL;
pq->size = 0;
}
销毁队列
队列中的每一个结点所占用的内存空间都是动态开辟的,当我们使用完队列后需要及时释放队列中的每一个结点。
cpp
// 队列的销毁
void QueueDestroy(Queue *pq) {
assert(pq);
//接收队头
QNode *cur = pq->front;
//循环遍历队列销毁
while (cur) {
QNode *nextnode = cur->next;
free(cur);
cur = nextnode;
}
//初始化
pq->front = pq->rear = NULL;
pq->size = 0;
}
入队列
入队列,即申请一个新结点并将其链接到队尾,然后改变队尾的指针指向即可。需要注意的是:若队列中原本无数据,那么我们只需让队头和队尾均指向这个新申请的结点即可。
cpp
// 进队列
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->front == NULL) {
pq->front = pq->rear = newnode;
} else {
pq->rear->next = newnode;
pq->rear = newnode;
}
pq->size++;
}
出队列
出队列,即释放队头指针指向的结点并改变队头指针的指向即可。若队列中只有一个结点,那么直接将该结点释放,然后将队头和队尾置空即可。
cpp
// 出队列
void QueuePop(Queue *pq) {
assert(pq);
assert(!QueueEmpty(pq));
// 当只有一个数据的时候,free掉后 front和rear都设置为NULL
if (pq->front->next == NULL) {
free(pq->front);
pq->front = pq->rear = NULL;
} else {
QNode *nextnode = pq->front->next;
free(pq->front);
pq->front = nextnode;
}
pq->size--;
}
获取队列头部元素
获取队列头部元素,即返回队头指针指向的数据即可。
cpp
// 取队头数据
QDataType QueueFront(Queue *pq) {
assert(pq);
assert(!QueueEmpty(pq));
return pq->front->data;
}
获取队列尾部元素
获取队列尾部元素,即返回队尾指针指向的数据即可。
cpp
// 取队尾数据
QDataType QueueBack(Queue *pq) {
assert(pq);
assert(!QueueEmpty(pq));
return pq->rear->data;
}
检测队列是否为空
检测队列是否为空,即判断队头指针指向的内容是否为空。
cpp
// 判断队列是否为空
bool QueueEmpty(Queue *pq) {
assert(pq);
return pq->front == NULL && pq->rear == NULL;
}
获取队列中有效元素个数
cpp
// 队列长度
size_t QueueSize(Queue *pq) {
assert(pq);
return pq->size;
}
完整代码
cpp
#pragma once
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
typedef int QDataType;
// 队列里队头和队尾的结构
typedef struct QueueNode {
QDataType data; //数据域
struct QueueNode *next;//指针域
} QNode;
// 队列的结构
typedef struct Queue {
QNode *front;// 队头
QNode *rear; // 队尾
size_t size; // 队列长度
} Queue;
// 队列的初始化
void QueueInit(Queue *pq) {
assert(pq);
pq->front = NULL;
pq->rear = NULL;
pq->size = 0;
}
// 队列的销毁
void QueueDestroy(Queue *pq) {
assert(pq);
//接收队头
QNode *cur = pq->front;
//循环遍历队列销毁
while (cur) {
QNode *nextnode = cur->next;
free(cur);
cur = nextnode;
}
//初始化
pq->front = pq->rear = 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->front == NULL) {
pq->front = pq->rear = newnode;
} else {
pq->rear->next = newnode;
pq->rear = newnode;
}
pq->size++;
}
// 出队列
void QueuePop(Queue *pq) {
assert(pq);
assert(!QueueEmpty(pq));
// 当只有一个数据的时候,free掉后 front和rear都设置为NULL
if (pq->front->next == NULL) {
free(pq->front);
pq->front = pq->rear = NULL;
} else {
QNode *nextnode = pq->front->next;
free(pq->front);
pq->front = nextnode;
}
pq->size--;
}
// 取队头数据
QDataType QueueFront(Queue *pq) {
assert(pq);
assert(!QueueEmpty(pq));
return pq->front->data;
}
// 取队尾数据
QDataType QueueBack(Queue *pq) {
assert(pq);
assert(!QueueEmpty(pq));
return pq->rear->data;
}
// 判断队列是否为空
bool QueueEmpty(Queue *pq) {
assert(pq);
return pq->front == NULL && pq->rear == NULL;
}
// 队列长度
size_t QueueSize(Queue *pq) {
assert(pq);
return pq->size;
}
// 打印队列
void QueuePrint(Queue *pq) {
assert(pq);
while (!QueueEmpty(pq)) {
printf("%d->", QueueFront(pq));
QueuePop(pq);
}
printf("NULL");
}