堆和队列(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先出

典型应用场景:

  1. 队列实际中要保证公平排队的地方都可以用他,如抽号机
  2. 广度优先遍历

二、堆(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

这里需要判断两种结果

  1. 如果数组的总容量是0,初始化分配4个容量
  2. 如果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,所以只从队头出数据
这里有两种情况需要注意

  1. 如果是只有一个节点,那么直接free掉当前节点
  2. 如果超过一个节点,先存储下个节点,然后将当前的节点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)数据结构的基本操作实现详解的主要内容,如果未能解决你的问题,请参考以下文章

C++stack&queue(栈队列优先级队列)

C++stack&queue(栈队列优先级队列)

C++stack&queue(栈队列优先级队列)

C++stack&queue(栈队列优先级队列)

C++栈和队列(stack&queue)

C++栈和队列(stack&queue)