栈和队列(保姆级+进阶)

栈的概念及结构

栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端 称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。

压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。 出栈:栈的删除操作叫做出栈。出数据也在栈顶

栈的实现

栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的 代价比较小。

//Stack.h
#pragma once
#include<stdlib.h>
#include<stdio.h>
#include<assert.h>
#include<stdbool.h>

typedef int DataType;
typedef struct Stack {
    DataType* data;
    int top;
    int capacity;
}Stack;

void StackInit(Stack* st);//初始化
void StackDestroy(Stack* st);//销毁
void StackPush(Stack* st, DataType x);// 放入数据
void StackPop(Stack* st);//删除数据
DataType StackTop(Stack* st);//取出栈顶数据
int StackSize(Stack* st);//栈中数据的个数
bool StackEmpty(Stack* st);//判空
//Stack.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "Stack.h"

void StackInit(Stack* st) {
    assert(st);
    st->data = NULL;
    st->capacity = st->top = 0;
}

//销毁
void StackDestroy(Stack* st) {
    assert(st);
    free(st->data);
    st->capacity = st->top = 0;
}
// 放入数据
void StackPush(Stack* st, DataType x) {
    assert(st);
    //检查容量
    if (st->capacity == st->top) {
        //增容
        int newCapacity = st->capacity == 0 ? 4 : st->capacity * 2;
        DataType* tmp = (DataType*)realloc(st->data, sizeof(DataType) * newCapacity);
        if (tmp == NULL) {
            perror("realloc");
            exit(-1);
        }
        st->data = tmp;
        st->capacity = newCapacity;
    }
    st->data[st->top] = x;
    st->top++;
}
//删除数据
void StackPop(Stack* st) {
    assert(st);
    assert(!StackEmpty(st));
    st->top--;
}
//取出栈顶数据
DataType StackTop(Stack* st) {
    assert(st);
    assert(!StackEmpty(st));
    return st->data[st->top - 1];
}
//统计栈中数据个数
int StackSize(Stack* st) {
    assert(st);
    return st->top;
}
//判空
bool StackEmpty(Stack* st) {
    assert(st);
    return st->top == 0;
}

队列

队列的概念及结构

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出 FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头

队列的实现

队列也可以用数组和链表的结构实现使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数 组头上出数据,效率会比较低(这涉及到数组数据向前挪动)

//Queue.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
typedef int DataType;
typedef struct QueueNode {
    DataType data;
    struct QueueNode* next;
}QueueNode;
typedef struct Queue {
    QueueNode* head;
    QueueNode* tail;
}Queue;

void QueueInit(Queue* q);// 初始化队列
void QueueDestroy(Queue* q);// 销毁队列
void QueuePush(Queue* q, DataType data);// 队尾入队列
void QueuePop(Queue* q);// 队头出队列
DataType QueueFront(Queue* q);// 获取队列头部元素
DataType QueueBack(Queue* q);// 获取队列队尾元素
int QueueSize(Queue* q);// 获取队列中有效元素个数
bool QueueEmpty(Queue* q);// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
//Queue.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "Queue.h"

// 初始化队列
void QueueInit(Queue* q) {
    assert(q);
    q->head = q->tail = NULL;
}
// 销毁队列
void QueueDestroy(Queue* q) {
    assert(q);
    QueueNode* cur = q->head;
    while (cur) {
        QueueNode* next = cur->next;
        free(cur);
        cur = next;
    }
    q->head = q->tail = NULL;
}
// 队尾入队列
void QueuePush(Queue* q, DataType data) {
    assert(q);
    QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
    if (newNode == NULL) {
        perror("malloc");
        exit(-1);
    }
    newNode->data = data;
    newNode->next = NULL;
    if (q->head == NULL) {
        q->head = q->tail = newNode;
    }
    else {
        q->tail->next = newNode;
        q->tail = newNode;
    }
}
// 队头出队列
void QueuePop(Queue* q) {
    assert(q && !QueueEmpty(q));
    QueueNode* next = q->head->next;
    free(q->head);
    q->head = next;
    if (q->head == NULL) {
        q->tail = NULL;
    }
}

// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
bool QueueEmpty(Queue* q) {
    return q->head == NULL;
}
// 获取队列头部元素
DataType QueueFront(Queue* q) {
    assert(q && !QueueEmpty(q));
    return q->head->data;
}
// 获取队列队尾元素
DataType QueueBack(Queue* q) {
    assert(q && !QueueEmpty(q));
    return q->tail->data;
}
// 获取队列中有效元素个数
int QueueSize(Queue* q) {
    assert(q);
    QueueNode* cur = q->head;
    int count = 0;
    while (cur) {
        count++;
        cur = cur->next;
    }
    return count;
}

循环队列

实际中我们有时还会使用一种队列叫循环队列。如操作系统课程讲解生产者消费者模型 时可能就会使用循环队列。环形队列可以使用数组实现,也可以使用循环链表实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。

循环队列符合先进先出且空间大小固定

循环队列无论是用数组实现还是链表实现,都要多开一个空间,否则无法判满和判空

下图中定义的front记录队头,tail记录队尾。k代表队列的大小。

//定义循环队列
typedef struct {
    int* a;//循环队列
    int front;//记录队头
    int tail;//记录队尾
    int k;//记录队列大小
} MyCircularQueue;
bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueIsFull(MyCircularQueue* obj);
MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue* cq = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    cq->a=(int*)malloc(sizeof(int)*(k+1));
    cq->front=cq->tail=0;
    cq->k=k;
    return cq;
}

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    if(myCircularQueueIsFull(obj)){
        return false;
    }
    obj->a[obj->tail]=value;
    obj->tail++;
    obj->tail%=obj->k+1;
    return true; 
}

bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj)){
        return false;
    }
    obj->front++;
    obj->front %= obj->k+1;
    return true;
}

int myCircularQueueFront(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj)){
        return -1;
    }
    return obj->a[obj->front];
}

int myCircularQueueRear(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj)){
        return -1;
    }
    if(obj->tail==0){
        return obj->a[obj->k];
    }
    else{
        return obj->a[obj->tail-1];
    }
}

bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    return obj->front==obj->tail;
}

bool myCircularQueueIsFull(MyCircularQueue* obj) {
    return (obj->tail+1)%(obj->k+1)==obj->front;
}

void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->a);
    free(obj);
}

队列实现栈

我们知道栈是先入后出的特点,而队列是先入先出的特点。我们要使用队列实现栈,需要使用两个队列,实现先入后出

因为我们是使用队列实现栈,因此我们需要先实现一个队列,在实现过程中使用队列中函数的接口。

typedef int DataType;

typedef struct QueueNode {
    struct QueueNode* next;
    DataType data;
}QueueNode;

typedef struct Queue {
    QueueNode* head;
    QueueNode* tail; 
}Queue;

void QueueInit(Queue* q);// 初始化队列
void QueueDestroy(Queue* q);// 销毁队列
void QueuePush(Queue* q, DataType data);// 队尾入队列
void QueuePop(Queue* q);// 队头出队列
DataType QueueFront(Queue* q);// 获取队列头部元素
DataType QueueBack(Queue* q);// 获取队列队尾元素
int QueueSize(Queue* q);// 获取队列中有效元素个数
bool QueueEmpty(Queue* q);// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 

// 初始化队列
void QueueInit(Queue* q) {
    assert(q);
    q->head = NULL;
    q->tail = NULL;
}
// 销毁队列
void QueueDestroy(Queue* q) {
    QueueNode* cur = q->head;
    while (cur) {
        QueueNode* next = cur->next;
        free(cur);
        cur = next; 
    }
    q->head = q->tail = NULL;
}
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
bool QueueEmpty(Queue* q) {
    assert(q);
    return q->head == NULL;
}
//创建新节点
QueueNode* BuyNewNode(DataType x) {
    QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
    if (newNode == NULL) {
        perror("malloc");
        exit(-1);
    }
    newNode->data = x;
    newNode->next = NULL;
    return newNode;
}
// 队尾入队列
void QueuePush(Queue* q, DataType data) {
    assert(q);
    QueueNode* newNode = BuyNewNode(data);
    if (q->head == NULL) {
        q->head = q->tail = newNode;
    }
    else {
        q->tail->next = newNode;
        q->tail = newNode;
    }
}
// 队头出队列
void QueuePop(Queue* q) {
    assert(q && !QueueEmpty(q));
    QueueNode* next = q->head->next;
    free(q->head);
    q->head = next;
    if (q->head == NULL) {
        q->tail = NULL;
    }
}
// 获取队列头部元素
DataType QueueFront(Queue* q) {
    assert(q && !QueueEmpty(q));
    return q->head->data;
}
// 获取队列队尾元素
DataType QueueBack(Queue* q) {
    assert(q && !QueueEmpty(q));
    return q->tail->data;
}
// 获取队列中有效元素个数
int QueueSize(Queue* q) { 
    assert(q && !QueueEmpty(q));
    QueueNode* cur = q->head;
    int count = 0;
    while (cur) {
        count++;
        cur = cur->next;
    }
    return count;
}

完成一个队列的实现之后,我们需要定义我们需要使用的两个队列,并完成这两个队列的初始化。

typedef struct {
    Queue q1;
    Queue q2;
} MyStack;

MyStack* myStackCreate() {
    MyStack* st=(MyStack*)malloc(sizeof(MyStack));
    QueueInit(&(st->q1));//这里的初始化使用的队列初始化函数的接口
    QueueInit(&(st->q2));
    return st;
}

栈的数据插入

选择一个队列入数据,另外的一个队列保持为空,方便后边栈的删除操作,具体请向下继续浏览。

//MyStackPush操作
void myStackPush(MyStack* obj, int x) {
    if(!QueueEmpty(&(obj->q1))){
        QueuePush(&(obj->q1),x);
    }
    else{
        QueuePush(&(obj->q2),x);
    }
}

栈顶数据的删除

将非空队列中的数据头出,存到非空的队列中,直至非空队列剩下最后一个元素。这时只用将这最后的元素取出来,再pop掉,就行了。

int myStackPop(MyStack* obj) {
    Queue* emptyQueue=&(obj->q1);
    Queue* nonEmptyQueue=&(obj->q2);
    if(!QueueEmpty(&(obj->q1))){
        emptyQueue=&(obj->q2);
        nonEmptyQueue=&(obj->q1);
    }
    while(QueueSize(nonEmptyQueue)>1){
        QueuePush(emptyQueue,QueueFront(nonEmptyQueue));
        QueuePop(nonEmptyQueue);
    }
    int top=QueueFront(nonEmptyQueue);
    QueuePop(nonEmptyQueue);
    return top;
}

栈的拿到栈顶元素

我们可以注意到,在栈的入数据的时候,我们使用一个队列入数据,保持另一个队列为空。在栈的删除数据的时候,我们将非空队列中的数据都移到了空队列中,再将非空队列中的最后一个元素删除了。也就是说栈的入数据、删除数据后,两个队列始终是有一个队列为空,另一个队列非空。并且在两个队列移动数据的时候,数据的顺序并没有发生变化。而我们现在是要拿栈顶元素,只用找到非空队列,拿到尾元素,就是我们需要拿到的栈顶元素。

int myStackTop(MyStack* obj) {
    if(!QueueEmpty(&(obj->q1))){
        return QueueBack(&(obj->q1)); 
    }
    else{
        return QueueBack(&(obj->q2));
    }
}

栈的判空

我们需要对两个队列都为空,则栈为空

bool myStackEmpty(MyStack* obj) {
    return QueueEmpty(&(obj->q1)) && QueueEmpty(&(obj->q2));
}

栈的销毁

我们使用了两个队列,需要将其分别销毁,再释放掉栈。

void myStackFree(MyStack* obj) {
    QueueDestroy(&(obj->q1));
    QueueDestroy(&(obj->q2));
    free(obj);
}

栈实现队列

栈的特性是先入后出,队列的特性是先入先出。这里需要使用两个栈来实现队列的先入先出

和上边的一样我们实现队列需要使用两个栈,我们需要先实现一个栈,在实现的过程中使用栈的接口。

typedef int DataType;
typedef struct Stack {
    DataType* a;
    int top;
    int capacity;
}Stack;

void StackInit(Stack* ps);//初始化
void StackDestroy(Stack* ps);//销毁
void StackPush(Stack* ps, DataType x);// 放入数据
void StackPop(Stack* ps);//删除数据
DataType StackTop(Stack* ps);//取出栈顶数据
int StackSize(Stack* ps);//栈中数据的个数
bool StackEmpty(Stack* ps);//判空

//初始化
void StackInit(Stack* ps) {
    assert(ps);
    ps->a = NULL;
    ps->top = 0;//top给的是0,表示top指向的是栈顶数据的下一个。
                //top给的是-1,表示top指向的是栈顶数据。
    ps->capacity = 0;
}
//销毁
void StackDestroy(Stack* ps) {
    assert(ps);
    free(ps->a);
    ps->a = NULL;
    ps->top = ps->capacity = 0;
}
//判空
bool StackEmpty(Stack* ps) {
    assert(ps);
    return ps->top == 0;
}
//增加数据
void StackPush(Stack* ps, DataType x) {
    assert(ps);
    if (ps->top == ps->capacity) {
        //增容
        int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
        DataType* tmp = (DataType*)realloc(ps->a, sizeof(DataType) * newCapacity);
        if (tmp == NULL) {
            perror("realloc");
            exit(-1);
        }
        ps->a = tmp;
        ps->capacity = newCapacity;
    }
    ps->a[ps->top] = x;
    ps->top++;
}
//删除数据
void StackPop(Stack* ps) {
    assert(ps);
    assert(!StackEmpty(ps));
    ps->top--;
}
//取出栈顶数据
DataType StackTop(Stack* ps) {
    assert(ps);
    assert(!StackEmpty(ps));
    return ps->a[ps->top - 1];
}
//栈中数据的个数
int StackSize(Stack* ps) {
    assert(ps);
    return ps->top;
}

实现队列的过程中,我们需要使用两个栈,我们需要首先定义这两个栈并完成其初始化。

typedef struct {
    Stack stPush;
    Stack stPop;
} MyQueue;

MyQueue* myQueueCreate() {
    MyQueue* q = (MyQueue*)malloc(sizeof(MyQueue));
    StackInit(&(q->stPush));
    StackInit(&(q->stPop));
    return q;
}

队列的入数据

可以留意一下我们在初始化两个栈的时候,分别命名尾stPush和stPop,也就是说我们有一个栈单独用来入数据,另外一个栈用来出数据。

void myQueuePush(MyQueue* obj, int x) {
    StackPush(&(obj->stPush),x);
}

队列的删除数据

我们入数据的时候,一直在一个栈中入数据,另外一个栈为空。我们需要将stPush栈中的全部数据存到stPop栈中,再删除栈顶数据。就完成了队头数据的删除了。

int myQueuePop(MyQueue* obj) {
    //如果stpop中没有数据,将stpush的数据导过去
    //stpop中的数据就符合先进先出的顺序了
    if(StackEmpty(&(obj->stPop))){
        while(!StackEmpty(&(obj->stPush))){
            StackPush(&(obj->stPop),StackTop(&(obj->stPush)));
            StackPop(&(obj->stPush));
        }
    }
    int front = StackTop(&(obj->stPop));
    StackPop(&(obj->stPop));
    return front;
}

队列拿到头元素

我们注意一下,将stPush栈中的数据拿到stPop栈中的时候,数据的顺序都颠倒了,因此拿到stPop栈中的数据,我们直接取头元素就能拿到队列的头元素了。

int myQueuePeek(MyQueue* obj) {
     //如果stpop中没有数据,将stpush的数据导过去
    //stpop中的数据就符合先进先出的顺序了
    if(StackEmpty(&(obj->stPop))){
        while(!StackEmpty(&(obj->stPush))){
            StackPush(&(obj->stPop),StackTop(&(obj->stPush)));
            StackPop(&(obj->stPush));
        }
    }
    return StackTop(&(obj->stPop));
}

队列的判空

我们使用了两个栈,需要对两个栈都进行判空。

bool myQueueEmpty(MyQueue* obj) {
    return StackEmpty(&(obj->stPush)) &&StackEmpty(&(obj->stPop));
}

队列的销毁

我们使用了两个栈,都需要进行销毁。再释放掉队列。

void myQueueFree(MyQueue* obj) {
    StackDestroy(&(obj->stPush));
    StackDestroy(&(obj->stPop));
    free(obj);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值