堆和队列(Stack && Queue)数据结构的基本操作实现详解
Posted SuchABigBug
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了堆和队列(Stack && Queue)数据结构的基本操作实现详解相关的知识,希望对你有一定的参考价值。
一、前言
今天介绍一下栈和堆
栈:是一种特殊的线性表,只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶。另一段为栈底
栈的数据遵守后进先出LIFO(last in first out)的原则
压栈:栈的插入操作叫做进栈/压栈,如数据在栈顶
出栈:栈的删除操作叫出栈,出数据也在栈顶
这里有很多人问栈和队列属于线性结构吗?可以用链表实现吗?
答案是肯定的,两者都可实现栈和队列,数据结构需要适配不同的应用场景。而顺序表和链表没有说哪个最好,都各有千秋。
顺序表在内存中是连续存储的,密度高,但是空间受限。
链式结构不受限于空间,随意扩容,但是存储是不连续的,密度低
堆:只允许在一端进行插入操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出First in first out
入队列:进行插入操作的一端成为队尾
出队列:进行删除操作的一端成为队头
栈和队列的出入顺序区别:
而队列,一种入队顺序,一种出队顺序
举个例子:
有的题目会问一个栈的入栈序列为ABCDE,出栈序列可能是什么呢?
可能是ABCDE,因为A先进,然后A出,B进B出,依次类推
可能是ABC入,然后CBA出,DE再进,ED出
但不可能是ECDBA,因为E先出,说明所有元素都已入栈,那么不可能是C先出而是D先出
典型应用场景:
- 队列实际中要保证公平排队的地方都可以用他,如抽号机
- 广度优先遍历
二、堆(stack)
1. 整体设计框架
还是和之前一样分为三个文件,而不是写在一个main函数里面,这样可读性更高,便于调试
main.c 专门用于函数调用、调试
stack.h 只用于函数声明
stack.c 用于函数实现
2. 函数实现
这里我们用数组进行数据存储
2.1 head File
我们先来看一下头文件的架构以及需要实现的函数功能
typedef int STDatatype;
typedef struct Stack{
STDatatype* a;
int top; //栈顶 , 类似于size
int capacity;
}ST;
void StackInit(ST* ps);
void StackDestory(ST* ps);
void StackPush(ST* ps, STDatatype x);
void StackPop(ST* ps);
bool StackEmpty(ST* ps);
int StackSize(ST* ps);
STDatatype StackTop(ST* ps); //取栈顶的数值
这里的STDataType是便于后期全局修改数据类型
Stack数据结构里有三个成员,第一个成员a为动态数组,后续进行空间开辟,top是指stack中的栈顶也就是数组的最后一个值,capacity为数组的总容量
2.2 StackInit
void StackInit(ST* ps){
assert(ps); //先断言,给的结构体是否为空,是空那就不用玩了,因此这里避免用户使用误传NULL
ps->a = NULL;
ps->top = 0;
ps->capacity = 0;
}
2.3 StackDestory
如果a数组不为空,free整个数组,并将数组a置空
void StackDestory(ST* ps){
assert(ps);
if(ps->a){
free(ps->a);
}
ps->a = NULL;
ps->top = 0;
ps->capacity = 0;
}
2.4 StackPush
这里需要判断两种结果
- 如果数组的总容量是0,初始化分配4个容量
- 如果capacity不为空,并且总size和capacity一样大,那么我们就进行扩容操作,增大到原来的两倍
void StackPush(ST* ps, STDatatype x){
assert(ps);
//检查空间够不够
if(ps->top == ps->capacity){
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity*2;
STDatatype* tmp = (STDatatype*)realloc(ps->a, sizeof(STDatatype)*newcapacity);
if(tmp == NULL){
printf("Realloc failed \\n");
exit(-1);
}
//如果不为空,给a
ps->a = tmp;
ps->capacity = newcapacity;
}
ps->a[ps->top] = x; //如果top的初始值为0,那么下标从0开始,如果下标从-1开始,那么需要++后,再赋值
ps->top++;
}
2.5 StackEmpty
判断stack是否为空
bool StackEmpty(ST* ps){
assert(ps);
return ps->top == 0;
}
2.6 StackPop
直接将数组的size-1即可实现pop操作
void StackPop(ST* ps){
assert(ps); //要删也要保证不为空
assert(!StackEmpty(ps));
ps->top--;
}
2.7 StackSize
int StackSize(ST* ps){
assert(ps);
return ps->top;
}
2.8 StackTop
访问数组中的数值需要注意一下top是从下标0开始的,因此需要top-1
//取栈顶的数值
STDatatype StackTop(ST* ps){
assert(ps);
assert(!StackEmpty(ps)); //如果我已经为空了,就不能调top了
return ps->a[ps->top-1];
}
3. 完整代码
Gitee链接🔗 🔗 🔗
👉 👉 👉 Stack Folder 👈 👈 👈
三、队列(Queue)
1. 整体设计框架
main.c 专门用于函数调用、调试
queue.h 只用于函数声明
queue.c 用于函数实现
2. 函数实现
2.1 head File
队列的数据结构和堆不太一样,QueueNode采用链式结构,Queue中有两个成员分别为head和tail
typedef int QDataType;
typedef struct QueueNode{
struct QueueNode* next;
QDataType x;
}QueueNode;
typedef struct Queue{
QueueNode* phead;
QueueNode* ptail;
}Queue;
void QueueInit(Queue* pq);
void QueueDestory(Queue* pq);
void QueuePush(Queue* pq, QDataType x); //从队尾入
void QueuePop(Queue* pq); //出数据,从队头
int QueueSize(Queue* pq);
QDataType QueueFront(Queue* pq);
QDataType QueueBack(Queue* pq);
bool QueueEmpty(Queue* pq);
2.2 QueueInit
对头和尾节点进行初始化
void QueueInit(Queue* pq){
assert(pq);
pq->phead = pq->ptail = NULL;
}
2.3 QueueDestory
void QueueDestory(Queue* pq){
assert(pq);
QueueNode* cur = pq->phead;
while(cur){
QueueNode* next = cur->next;
free(cur);
cur= next;
}
pq->phead = pq->ptail = NULL;
}
2.4 QueuePush
push一个新的节点进去前首先有创建一个新的节点
然后再去判断尾节点是否为空,如果是空那么将头节点和尾节点同时指向新节点
如果不为空,只需要挪动尾节点即可
//从队尾入
void QueuePush(Queue* pq, QDataType x){
assert(pq);
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
if(newnode == NULL){
printf("failed to malloc \\n");
exit(-1);
}
newnode->x =x;
newnode->next = NULL;
if(pq->ptail == NULL){
pq->phead = pq->ptail = newnode;
}else{
pq->ptail->next = newnode;
pq->ptail = newnode;
}
}
2.5 QueuePop
queue属性前面讲过,FIFO,所以只从队头出数据
这里有两种情况需要注意
- 如果是只有一个节点,那么直接free掉当前节点
- 如果超过一个节点,先存储下个节点,然后将当前的节点free掉就实现了pop功能
//出数据,从队头
void QueuePop(Queue* pq){
assert(pq);
assert(!QueueEmpty(pq));
if(pq->phead->next == NULL){
free(pq->phead);
pq->phead = pq->ptail = NULL;
}else{
QueueNode* next= pq->phead->next;
free(pq->phead);
pq->phead = next;
}
}
2.6 QueueSize
int QueueSize(Queue* pq){
assert(pq);
int n=0;
QueueNode* cur = pq->phead;
while(cur){
n++;
cur = cur->next;
}
return n;
}
2.7 QueueFront
返回队头的值
QDataType QueueFront(Queue* pq){
assert(pq);
assert(!QueueEmpty(pq));
return pq->phead->x;
}
2.8 QueueBack
返回队尾的值
QDataType QueueBack(Queue* pq){
assert(pq);
assert(!QueueEmpty(pq));
return pq->ptail->x;
}
2.9 QueueEmpty
如果头和尾都为NULL才返回true
bool QueueEmpty(Queue* pq){
assert(pq);
return pq->ptail == NULL && pq->phead == NULL;
}
3. 完整代码
Gitee链接🔗 🔗 🔗
👉 👉 👉 Queue Folder 👈 👈 👈
创作不易,如果文章对你帮助的话,点赞三连哦:)
以上是关于堆和队列(Stack && Queue)数据结构的基本操作实现详解的主要内容,如果未能解决你的问题,请参考以下文章