《数据结构:栈与队列底层原理》

hello大家好 欢迎来到小四季豆的博客


通过对链表的学习,我们清楚了链表是一种灵活的线性存储结构。

此篇我们将要学习两个受限线性数据结构---栈与队列

一、基础概念

1.1 栈

栈是一种遵循"后进先出"(LIFO, Last In First Out)原则的线性数据结构。最后进入栈的数据先出栈。

  • 核心结构:
  1. 栈顶(Top):栈操作的唯一入口
  2. 栈底(Bottom):栈的另一端
  3. 栈元素:存放在栈里每一个独立的数据单元
  • 基础操作:

初始化、判空、判满、入栈、出栈、取栈顶、销毁

注意:所有插入、删除、读取操作仅在栈顶完成

1.2 队列

队列是一种遵循"先进先出"(FIFO, First In First Out)原则的线性数据结构。最先进入队列的数据先出队。

  • 核心结构:
  1. 队头(Front):队列取出数据的一端
  2. 队尾(Rear):队列存入数据的一端
  3. 队列元素:存放在队列里每一个独立的数据单元
  • 基础操作:初始化、判空、判满、入队、出队、取队头、销毁

注意:所有插入操作仅在队尾完成,所有删除、读取操作仅在队头完成

二、底层逻辑与实现

2.1 栈

栈的底层实现分为顺序栈(基于数组实现) 和**链式栈(基于链表实现)**两种逻辑结构

通常情况下优先用顺序栈

数组下标可以直接定位栈顶入栈出栈极其方便,顺序栈实现简单,代码量少,数组仅存数据空间开销小。

链表出栈入栈需要遍历寻址,程序运行还要管理结点,指针,内存的释放,代码复杂。

2.1.1 顺序栈

  • 结构特点
    1. 底层使用一个数组。
    2. 维护一个指针或索引 top,始终指向栈顶元素(或者指向栈顶元素的下一个空位,具体取决于实现习惯)。
    3. 入栈(Push) :将元素放入 top 指向的位置,然后 top 加 1。时间复杂度:O(1)
    4. 出栈(Pop)top 减 1,然后取出该位置的元素。时间复杂度:O(1)
  • 头文件
cpp 复制代码
#ifndef STACKSEQ_H
#define STACKSEQ_H

#include <stdio.h>
#include <stdlib.h>

typedef int STDataType;

typedef struct SeqStack
{
    STDataType* arr;    // 动态数组存储元素
    int top;            // 栈顶下标,top=-1代表栈空
    int capacity;       // 数组当前总容量
} SeqStack;

void SeqStackInit(SeqStack* st);
void SeqStackPush(SeqStack* st, STDataType x);
void SeqStackPop(SeqStack* st);
STDataType SeqStackTop(SeqStack* st);
int SeqStackEmpty(SeqStack* st);
void SeqStackDestroy(SeqStack* st);
void SeqStackPrint(SeqStack* st);

#endif
  • 接口实现文件
cpp 复制代码
#include "StackSeq.h"

void SeqStackInit(SeqStack* st)
{
    // 初始容量直接写4,删除宏定义
    st->arr = (STDataType*)malloc(sizeof(STDataType) * 4);
    if (st->arr == NULL)
    {
        perror("malloc fail");
        exit(EXIT_FAILURE);
    }
    st->top = -1;         // 初始化栈顶,标识空栈
    st->capacity = 4;
}

static void SeqStackExpand(SeqStack* st)
{
    STDataType* tmp = (STDataType*)realloc(st->arr, sizeof(STDataType) * st->capacity * 2);
    if (tmp == NULL)
    {
        perror("realloc fail");
        exit(EXIT_FAILURE);
    }
    st->arr = tmp;
    st->capacity *= 2;
}

void SeqStackPush(SeqStack* st, STDataType x)
{
    // 判断是否栈满
    if (st->top + 1 == st->capacity)
    {
        SeqStackExpand(st);
    }
    st->top++;               // 栈顶上移
    st->arr[st->top] = x;   // 存入数据
}

void SeqStackPop(SeqStack* st)
{
    // 空栈禁止出栈
    if (SeqStackEmpty(st))
    {
        printf("栈为空,无法出栈\n");
        return;
    }
    st->top--;               // 栈顶下移完成出栈
}

STDataType SeqStackTop(SeqStack* st)
{
    if (SeqStackEmpty(st))
    {
        printf("栈为空,无栈顶元素\n");
        exit(EXIT_FAILURE);
    }
    return st->arr[st->top]; // 返回栈顶数据
}

int SeqStackEmpty(SeqStack* st)
{
    return st->top == -1;    // top=-1判定栈空
}

void SeqStackDestroy(SeqStack* st)
{
    free(st->arr);           // 释放堆数组
    st->arr = NULL;
    st->top = 0;
    st->capacity = 0;
}

void SeqStackPrint(SeqStack* st)
{
    if (SeqStackEmpty(st))
    {
        printf("栈为空\n");
        return;
    }
    printf("栈元素(栈底→栈顶):");
    // 遍历打印全部元素
    for (int i = 0; i <= st->top; i++)
    {
        printf("%d ", st->arr[i]);
    }
    printf("\n");
}

2.1.2 链式栈

  • 结构特点
    1. 底层使用单链表(通常不需要双向链表,因为栈只在头部操作)。
    2. 维护一个指针 top,始终指向链表的头节点。
    3. 入栈(Push) :在链表头部插入新节点,更新 top 指针。
    4. 出栈(Pop) :删除头节点,更新 top 指针指向下一个节点。
  • 头文件
cpp 复制代码
#ifndef STACKLINK_H
#define STACKLINK_H

#include <stdio.h>
#include <stdlib.h>

typedef int STDataType;

typedef struct StackNode
{
    STDataType data;        // 结点数据
    struct StackNode* next; // 后继结点指针
} StackNode;

typedef struct LinkStack
{
    StackNode* head;        // 链表头指针(栈顶)
} LinkStack;

void LinkStackInit(LinkStack* st);
void LinkStackPush(LinkStack* st, STDataType x);
void LinkStackPop(LinkStack* st);
STDataType LinkStackTop(LinkStack* st);
int LinkStackEmpty(LinkStack* st);
void LinkStackDestroy(LinkStack* st);
void LinkStackPrint(LinkStack* st);

static StackNode* BuyNode(STDataType x);

#endif
  • 接口实现文件
cpp 复制代码
#include "StackLink.h"

static StackNode* BuyNode(STDataType x)
{
    // 新建结点分配内存
    StackNode* newnode = (StackNode*)malloc(sizeof(StackNode));
    if (newnode == NULL)
    {
        perror("malloc fail");
        exit(EXIT_FAILURE);
    }
    newnode->data = x;
    newnode->next = NULL;
    return newnode;
}

void LinkStackInit(LinkStack* st)
{
    st->head = NULL; // 头指针置空,代表空栈
}

void LinkStackPush(LinkStack* st, STDataType x)
{
    StackNode* newnode = BuyNode(x);
    newnode->next = st->head; // 新结点指向原头结点
    st->head = newnode;       // 更新头指针为新结点
}

void LinkStackPop(LinkStack* st)
{
    // 空栈禁止出栈
    if (LinkStackEmpty(st))
    {
        printf("栈为空,无法出栈\n");
        return;
    }
    StackNode* del = st->head;
    st->head = st->head->next; // 头指针后移
    free(del);                 // 释放被删除结点
}

STDataType LinkStackTop(LinkStack* st)
{
    if (LinkStackEmpty(st))
    {
        printf("栈为空,无栈顶元素\n");
        exit(EXIT_FAILURE);
    }
    return st->head->data; // 返回头结点数据(栈顶)
}

int LinkStackEmpty(LinkStack* st)
{
    return st->head == NULL; // 头指针为空判定栈空
}

void LinkStackDestroy(LinkStack* st)
{
    StackNode* cur = st->head;
    // 逐个释放所有结点
    while (cur != NULL)
    {
        StackNode* next = cur->next;
        free(cur);
        cur = next;
    }
    st->head = NULL;
}

void LinkStackPrint(LinkStack* st)
{
    if (LinkStackEmpty(st))
    {
        printf("栈为空\n");
        return;
    }
    StackNode* cur = st->head;
    printf("栈元素(栈顶→栈底):");
    // 遍历链表打印
    while (cur != NULL)
    {
        printf("%d ", cur->data);
        cur = cur->next;
    }
    printf("\n");
}

2.2 队列

队列的底层实现分为顺序队列(基于数组实现) 和**链式队列(基于链表实现)**两种逻辑结构

队列的实现选择:

顺序队列:已知最大数据量、频繁创建销毁

链式队列:数据量不确定,频繁扩容缩容

2.2.1 顺序队列

  • 底层结构:静态数组(如 int data[MAX_SIZE])。
  • 指针管理:维护两个整型下标(或指针):
    • 队头指针 (front):指向队列中第一个元素的位置。
    • 队尾指针 (rear):指向队列中最后一个元素的下一个位置(即下一个新元素将要插入的位置)。
  • 入队/出队方向:
    • 入队 (Enqueue):在 rear 指向的位置存入数据,然后 rear 向后移动一位。
    • 出队 (Dequeue):取出 front 指向位置的数据,然后 front 向后移动一位。
  • 头文件
cpp 复制代码
#ifndef SEQQUEUE_H   //防止头文件重复包含
#define SEQQUEUE_H 

#include <stdbool.h>

// 队列最大容量
#define MAX_SIZE 100
// 数据类型,重命名方便后续数据类型的修改
typedef int datatype;

// 顺序队列结构体(内部静态数组)
typedef struct SeqQueue
{
    datatype arr[MAX_SIZE];
    int front;  // 队头下标
    int rear;   // 下一个入队位置下标
} SeqQueue;

// 函数声明
// 初始化队列
void InitSeqQueue(SeqQueue* q);

// 判断队列是否满
bool isFull(SeqQueue* q);

// 判断队列是否空
bool isEmpty(SeqQueue* q);

// 入队,成功返回true,队满返回false
bool enqueue(SeqQueue* q, datatype x);

// 出队,成功返回true,队空返回false
bool dequeue(SeqQueue* q);

// 获取队头元素,队空返回false
bool GetFront(SeqQueue* q, datatype* val);

// 遍历打印队列所有有效元素
void PrintSeqQueue(SeqQueue* q);

// 清空队列(静态数组无需释放内存,仅重置头尾指针)
void ClearSeqQueue(SeqQueue* q);

#endif
  • 接口实现文件
cpp 复制代码
#include "SeqQueue.h"
#include <assert.h>
#include <stdio.h>

//初始化队列
void InitSeqQueue(SeqQueue* q)
{
    assert(q);
    q->front = q->rear = 0;
}

//判满
bool isFull(SeqQueue* q)
{
    assert(q);
    return q->rear == MAX_SIZE;
}

//判空
bool isEmpty(SeqQueue* q)
{
    assert(q);
    return q->rear == q->front;
}

//入队
bool enqueue(SeqQueue* q, datatype x)
{
    assert(q);
    if (isFull(q))
    {
        return false;
    }
    q->arr[q->rear] = x;
    ++q->rear;
    return true;
}

//出队
bool dequeue(SeqQueue* q)
{
    assert(q);
    if (isEmpty(q))
    {
        return false;
    }
    q->front++;
    return true;
}

//获取队头元素
bool GetFront(SeqQueue* q, datatype* val)
{
    assert(q && val);
    if (isEmpty(q))
        return false;
    *val = q->arr[q->front];
    return true;
}

//打印队列有效元素
void PrintSeqQueue(SeqQueue* q)
{
    assert(q);
    printf("队列元素:");
    for (int i = q->front; i < q->rear; i++)
    {
        printf("%d ", q->arr[i]);
    }
    printf("\n");
}

//清空队列
void ClearSeqQueue(SeqQueue* q)
{
    assert(q);
    q->front = q->rear = 0;
}

在普通数组中,出队后前面的空间无法被复用,会发生假溢出(有内存而无法使用)

我们可以通过循环队列来对这个不足进行优化,底层依然是数组,但逻辑上首尾相连。

通过取模运算**%数组容量**来更新指针,让front和rear走到数组末尾后折返到数组头部,复用前面队空出来的位置

设数组最大容量 MaxSize

  • front:队头指针,指向队首元素位置
  • rear:队尾指针,指向下一个待插入元素的位置

通过图片我们可以看出循环队列的判空和判满条件都是front == rear

我们可以牺牲一个空间来区分"队空"和"队满",队列最多储存**MaxSize-1**个元素

队空:front == rear

队满:(rear + 1) % MaxSize == front

  • 头文件
cpp 复制代码
#ifndef CIRCLEQUEUE_H
#define CIRCLEQUEUE_H

#include <stdio.h>
#include <assert.h>
#include <stdbool.h>

// 数组最大容量
#define MaxSize 8
// 存储数据类型:整型
typedef int CQDataType;

// 循环队列结构体
typedef struct
{
    CQDataType arr[MaxSize];
    int front;  // 队头下标
    int rear;   // 队尾下一个位置下标
} CQueue;

// 初始化队列
void CQInit(CQueue* cq);
// 清空重置队列
void CQDestroy(CQueue* cq);

// 判断队空
bool CQEmpty(CQueue* cq);
// 判断队满(牺牲一个位置判满)
bool CQFull(CQueue* cq);

// 入队:成功返回true,满返回false
bool CQPush(CQueue* cq, CQDataType val);
// 出队:成功返回true,空返回false
bool CQPop(CQueue* cq);

// 获取队头元素
CQDataType CQFront(CQueue* cq);
// 获取有效元素个数
int CQSize(CQueue* cq);
// 遍历打印队列所有有效数据
void CQPrint(CQueue* cq);

#endif
  • 接口实现文件
cpp 复制代码
#include "CircleQueue.h"

// 初始化队列:将队头、队尾下标置0
void CQInit(CQueue* cq)
{
    // 防止传入空指针,非法访问
    assert(cq);
    cq->front = 0;
    cq->rear = 0;
}

// 重置清空队列,静态数组不用free,只需复位头尾下标
void CQDestroy(CQueue* cq)
{
    assert(cq);
    cq->front = cq->rear = 0;
}

// 判断队列是否为空
bool CQEmpty(CQueue* cq)
{
    assert(cq);
    // front与rear重合代表没有有效元素
    return cq->front == cq->rear;
}

// 判断队列是否已满(牺牲一个空位判满方案)
bool CQFull(CQueue* cq)
{
    assert(cq);
    // rear往后走一格取模等于front,说明数组只剩一个空位,队列已满
    return (cq->rear + 1) % MaxSize == cq->front;
}

// 入队:在队尾插入一个元素
bool CQPush(CQueue* cq, CQDataType val)
{
    assert(cq);
    // 队列满则入队失败
    if (CQFull(cq))
    {
        return false;
    }
    // 将数据放入rear指向位置
    cq->arr[cq->rear] = val;
    // rear向后移动,取模实现循环
    cq->rear = (cq->rear + 1) % MaxSize;
    return true;
}

// 出队:删除队头元素
bool CQPop(CQueue* cq)
{
    assert(cq);
    // 队列为空无法出队
    if (CQEmpty(cq))
    {
        return false;
    }
    // front向后移动完成出队,取模循环
    cq->front = (cq->front + 1) % MaxSize;
    return true;
}

// 获取队头元素值
CQDataType CQFront(CQueue* cq)
{
    assert(cq);
    // 断言:队列为空直接报错,禁止取队头
    assert(!CQEmpty(cq));
    return cq->arr[cq->front];
}

// 计算当前队列有效元素数量
int CQSize(CQueue* cq)
{
    assert(cq);
    // 加上MaxSize再取模,避免rear < front时出现负数
    return (cq->rear - cq->front + MaxSize) % MaxSize;
}

// 遍历打印队列所有有效数据
void CQPrint(CQueue* cq)
{
    assert(cq);
    // 空队列直接提示
    if (CQEmpty(cq))
    {
        printf("队列为空\n");
        return;
    }
    // 从队头开始遍历
    int cur = cq->front;
    printf("队列元素:");
    // 循环终止条件:走到rear结束,只遍历有效区间[front, rear)
    while (cur != cq->rear)
    {
        printf("%d ", cq->arr[cur]);
        // 下标后移,循环取模
        cur = (cur + 1) % MaxSize;
    }
    printf("\n");
}

2.2.2 链式队列

  • 底层结构:通常使用单向链表。
    • 队头指针 (front):指向链表的第一个节点(队头)。
    • 队尾指针 (rear):指向链表的最后一个节点(队尾)。
  • 入队/出队方向:
    • 入队 (Enqueue):在链表的尾部插入新节点(由 rear 指针操作)。
    • 出队 (Dequeue):从链表的头部删除节点(由 front 指针操作)。
  • 动态内存:不需要预先分配固定大小的数组空间,按需申请和释放节点内存。
  • 头文件
cpp 复制代码
#ifndef LINKQUEUE_H
#define LINKQUEUE_H

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

// 存储数据类型
typedef int LQDataType;

// 队列节点结构
typedef struct LinkNode
{
    LQDataType data;
    struct LinkNode* next;
} LinkNode;

// 链式队列结构体:保存头、尾指针,方便头尾操作
typedef struct
{
    LinkNode* head;   // 头结点
    LinkNode* tail;   // 尾指针
} LinkQueue;

// 初始化链式队列(创建哨兵头结点)
void LQInit(LinkQueue* q);

// 销毁整个队列,释放所有节点内存
void LQDestroy(LinkQueue* q);

// 判断队列是否为空
bool LQEmpty(LinkQueue* q);

// 队尾入队
bool LQPush(LinkQueue* q, LQDataType val);

// 队头出队
bool LQPop(LinkQueue* q);

// 获取队头元素
LQDataType LQFront(LinkQueue* q);

// 统计队列有效元素个数
int LQSize(LinkQueue* q);

// 遍历打印队列所有元素
void LQPrint(LinkQueue* q);

#endif
  • 接口实现文件
cpp 复制代码
#include "LinkQueue.h"

// 创建一个新节点
static LinkNode* BuyNode(LQDataType val)
{
    LinkNode* newnode = (LinkNode*)malloc(sizeof(LinkNode));
    if (newnode == NULL)
    {
        perror("malloc fail");
        exit(-1);
    }
    newnode->data = val;
    newnode->next = NULL;
    return newnode;
}

// 初始化队列:创建哨兵头结点,头尾都指向头结点
void LQInit(LinkQueue* q)
{
    assert(q);
    q->head = BuyNode(0);
    q->tail = q->head;
}

// 释放全部节点,销毁队列
void LQDestroy(LinkQueue* q)
{
    assert(q);
    LinkNode* cur = q->head;
    while (cur != NULL)
    {
        LinkNode* next = cur->next;
        free(cur);
        cur = next;
    }
    q->head = q->tail = NULL;
}

// 判断队空:头结点下没有有效节点
bool LQEmpty(LinkQueue* q)
{
    assert(q);
    return q->head == q->tail;
}

// 尾插入队
bool LQPush(LinkQueue* q, LQDataType val)
{
    assert(q);
    LinkNode* newnode = BuyNode(val);
    q->tail->next = newnode;
    q->tail = newnode;
    return true;
}

// 头删出队
bool LQPop(LinkQueue* q)
{
    assert(q);
    if (LQEmpty(q))
    {
        return false;
    }
    // 第一个有效节点
    LinkNode* del = q->head->next;
    q->head->next = del->next;

    // 如果删完最后一个元素,尾指针归位到头结点
    if (q->tail == del)
    {
        q->tail = q->head;
    }
    free(del);
    return true;
}

// 获取队头数据
LQDataType LQFront(LinkQueue* q)
{
    assert(q);
    assert(!LQEmpty(q));
    return q->head->next->data;
}

// 统计节点个数
int LQSize(LinkQueue* q)
{
    assert(q);
    int count = 0;
    LinkNode* cur = q->head->next;
    while (cur != NULL)
    {
        count++;
        cur = cur->next;
    }
    return count;
}

// 遍历打印队列
void LQPrint(LinkQueue* q)
{
    assert(q);
    if (LQEmpty(q))
    {
        printf("队列为空\n");
        return;
    }
    LinkNode* cur = q->head->next;
    printf("队列元素:");
    while (cur != NULL)
    {
        printf("%d ", cur->data);
        cur = cur->next;
    }
    printf("\n");
}

三、经典算法题

3.1 有效的括号

https://leetcode.cn/problems/valid-parentheses/

  • 解题思路

遇到左括号直接入栈,遇到右括号,先判断栈是否为空,为空直接不匹配,返回错误。不为空取栈顶对比是否匹配,匹配弹出栈顶,不匹配直接返回错误。遍历结束后,栈为空说明括号全部匹配。

  • 代码实现
cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

// 自定义栈结构
typedef char STDataType;
typedef struct SeqStack
{
    STDataType* arr;
    int top;
    int capacity;
} SeqStack;

void SeqStackInit(SeqStack* st)
{
    st->arr = (STDataType*)malloc(sizeof(STDataType) * 4);
    st->top = -1;
    st->capacity = 4;
}

static void Expand(SeqStack* st)
{
    STDataType* tmp = realloc(st->arr, sizeof(STDataType) * st->capacity * 2);
    st->arr = tmp;
    st->capacity *= 2;
}

void Push(SeqStack* st, STDataType x)
{
    if (st->top + 1 == st->capacity)
        Expand(st);
    st->arr[++st->top] = x;
}

void Pop(SeqStack* st)
{
    st->top--;
}

STDataType Top(SeqStack* st)
{
    return st->arr[st->top];
}

bool Empty(SeqStack* st)
{
    return st->top == -1;
}

void Destroy(SeqStack* st)
{
    free(st->arr);
}

bool isValid(char * s){
    SeqStack st;
    SeqStackInit(&st);
    int i = 0;
    while(s[i] != '\0')
    {
        // 左括号入栈
        if(s[i] == '(' || s[i] == '{' || s[i] == '[')
        {
            Push(&st, s[i]);
        }
        else
        {
            // 右括号但栈空,直接错误
            if(Empty(&st))
            {
                Destroy(&st);
                return false;
            }
            char topch = Top(&st);
            if( (s[i]==')'&&topch=='(') || (s[i]=='}'&&topch=='{') || (s[i]==']'&&topch=='[') )
            {
                Pop(&st);
            }
            else
            {
                Destroy(&st);
                return false;
            }
        }
        i++;
    }
    bool ret = Empty(&st);
    Destroy(&st);
    return ret;
}

3.2 用栈实现队列

https://leetcode.cn/problems/implement-queue-using-stacks/

  • 解题思路

使用两个栈 :in栈负责入队,out栈负责出队

入队:直接push到in栈 ;出队/取队头:如果out栈为空,将in栈的元素全部导入out栈中,出队Pop out栈,取队头取out栈栈顶;判空:两个栈同时为空,队列就为空。

  • 代码实现
cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

// 栈存储元素类型重命名,方便后续修改类型
typedef int STDataType;

// 顺序栈结构体定义
typedef struct SeqStack
{
    STDataType* arr;   // 动态数组,存放栈元素
    int top;           // 栈顶下标,这里约定:top=-1 代表栈空
    int cap;           // 当前数组总容量
} SeqStack;

// 栈初始化
void StackInit(SeqStack* st)
{
    // 初始开辟4个int大小空间
    st->arr = malloc(sizeof(int) * 4);
    st->top = -1;       // 栈空标记
    st->cap = 4;        // 初始容量4
}

// 入栈:把x压入栈顶
void StackPush(SeqStack* st, int x)
{
    // 判断是否满:top+1等于容量,说明没有空余位置
    if (st->top + 1 == st->cap)
    {
        // 扩容为原来2倍
        st->arr = realloc(st->arr, st->cap * 2 * sizeof(int));
        st->cap *= 2;
    }
    // 先top自增,再赋值
    st->arr[++st->top] = x;
}

// 判断栈是否为空
bool StackEmpty(SeqStack* st)
{
    // top=-1 栈为空,返回true;否则false
    return st->top == -1;
}

// 获取栈顶元素(不删除)
int StackTop(SeqStack* st)
{
    return st->arr[st->top];
}

// 出栈:删除栈顶元素(只移动下标,不释放空间)
void StackPop(SeqStack* st)
{
    st->top--;
}

// 销毁栈:释放动态数组内存
void StackDestroy(SeqStack* st)
{
    free(st->arr);
    // 可选:置空防止野指针 st->arr = NULL;
}

// ====================== 两个栈实现队列 ======================
// 队列结构体:包含入栈、出栈两个栈
typedef struct {
    SeqStack in;    // 入队栈:所有新元素先压入这里
    SeqStack out;   // 出队栈:弹出队头元素用
} MyQueue;

// 创建队列,初始化两个内部栈
MyQueue* myQueueCreate() {
    MyQueue* q = malloc(sizeof(MyQueue));
    StackInit(&q->in);
    StackInit(&q->out);
    return q;
}

// 队列入队:直接往in栈压数据
void myQueuePush(MyQueue* obj, int x) {
    StackPush(&obj->in, x);
}

// 队列出队:删除并返回队首元素
int myQueuePop(MyQueue* obj) {
    // 如果out栈为空,需要把in栈所有元素全部倒进out栈
    if (StackEmpty(&obj->out))
    {
        // in不为空就持续搬运
        while (!StackEmpty(&obj->in))
        {
            // 取出in栈顶,压入out栈
            StackPush(&obj->out, StackTop(&obj->in));
            // in栈弹出该元素
            StackPop(&obj->in);
        }
    }
    // out栈顶就是队列头部
    int ret = StackTop(&obj->out);
    StackPop(&obj->out);
    return ret;
}

// 获取队首元素(不删除)
int myQueuePeek(MyQueue* obj) {
    // out为空则先倒数据
    if (StackEmpty(&obj->out))
    {
        while (!StackEmpty(&obj->in))
        {
            StackPush(&obj->out, StackTop(&obj->in));
            StackPop(&obj->in);
        }
    }
    return StackTop(&obj->out);
}

// 判断队列整体是否为空
bool myQueueEmpty(MyQueue* obj) {
    // 两个栈同时为空,队列才是空
    return StackEmpty(&obj->in) && StackEmpty(&obj->out);
}

// 释放队列所有内存
void myQueueFree(MyQueue* obj) {
    // 先销毁两个内部栈的数组
    StackDestroy(&obj->in);
    StackDestroy(&obj->out);
    // 再释放队列结构体本身
    free(obj);
}

3.3 用队列实现栈

https://leetcode.cn/problems/implement-stack-using-queues/

  • 解题思路

使用两个队列,q1,q2 入栈元素进入q1,出栈把 q1 中除最后一个元素外,全部转移到 q2,

弹出 q1 仅剩的元素(栈顶),每次出栈将q1与q2交换位置,保证q1始终为主队列。

  • 代码实现
cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

// 队列结点结构体
typedef struct QNode {
    int data;
    struct QNode *next;
} QNode;

// 队列结构体:头尾指针
typedef struct {
    QNode *front;   // 队头,出队用
    QNode *rear;    // 队尾,入队用
} Queue;

// ===================== 队列基础函数 =====================
// 初始化空队列
void InitQueue(Queue *q) {
    q->front = q->rear = NULL;
}

// 判断队列是否为空
int QueueEmpty(Queue *q) {
    return q->front == NULL;
}

// 入队
int EnQueue(Queue *q, int val) {
    QNode *newNode = (QNode*)malloc(sizeof(QNode));
    if (newNode == NULL) return 0; // 内存分配失败
    newNode->data = val;
    newNode->next = NULL;
    if (QueueEmpty(q)) {
        q->front = q->rear = newNode;
    } else {
        q->rear->next = newNode;
        q->rear = newNode;
    }
    return 1;
}

// 出队
int DeQueue(Queue *q, int *ret) {
    if (QueueEmpty(q)) return 0;
    QNode *temp = q->front;
    *ret = temp->data;
    q->front = q->front->next;
    free(temp);
    // 如果队列为空,尾指针置空
    if (q->front == NULL) {
        q->rear = NULL;
    }
    return 1;
}

// 获取队头元素(不删除)
int GetFront(Queue *q, int *ret) {
    if (QueueEmpty(q)) return 0;
    *ret = q->front->data;
    return 1;
}

// 销毁队列,释放所有内存
void DestroyQueue(Queue *q) {
    int tmp;
    while (!QueueEmpty(q)) {
        DeQueue(q, &tmp);
    }
}

// ===================== 用两个队列模拟栈 =====================
Queue q1, q2; // q1主力存数据,q2中转临时队列

// 栈初始化
void MyStackInit(void) {
    InitQueue(&q1);
    InitQueue(&q2);
}

// 入栈操作
void MyStackPush(int x) {
    EnQueue(&q1, x); // 新元素直接进入主队列q1
}

// 出栈操作,返回出栈元素
int MyStackPop(void) {
    int val;
    // 把q1除最后一个元素外,全部转移到q2
    while (q1.front != q1.rear) {
        DeQueue(&q1, &val);
        EnQueue(&q2, val);
    }
    // q1仅剩栈顶元素,弹出
    int topVal;
    DeQueue(&q1, &topVal);

    // 交换q1 q2,让q1继续作为主队列
    Queue temp = q1;
    q1 = q2;
    q2 = temp;

    return topVal;
}

// 获取栈顶元素(不出栈)
int MyStackTop(void) {
    int val;
    // 转移前n-1个元素到q2
    while (q1.front != q1.rear) {
        DeQueue(&q1, &val);
        EnQueue(&q2, val);
    }
    // 取出栈顶
    int topVal;
    GetFront(&q1, &topVal);
    // 把栈顶也移入q2
    DeQueue(&q1, &val);
    EnQueue(&q2, val);

    // 交换队列
    Queue temp = q1;
    q1 = q2;
    q2 = temp;

    return topVal;
}

// 判断栈是否为空
int MyStackEmpty(void) {
    return QueueEmpty(&q1);
}

// 销毁栈,释放内存
void MyStackDestroy(void) {
    DestroyQueue(&q1);
    DestroyQueue(&q2);
}

// ===================== 测试主函数 =====================
int main(void) {
    MyStackInit();

    MyStackPush(1);
    MyStackPush(2);
    MyStackPush(3);

    printf("栈顶:%d\n", MyStackTop());  // 输出3
    printf("出栈:%d\n", MyStackPop());  // 弹出3
    printf("栈顶:%d\n", MyStackTop());  // 输出2

    if (!MyStackEmpty()) {
        printf("栈非空\n");
    }

    MyStackDestroy();
    return 0;
}

到这里,栈与队列的基础概念、两种底层实现方式,以及三道高频 LeetCode 模拟习题就全部讲解完了

希望本篇内容能帮大家梳理巩固栈与队列~~ 我们下期再见

如果觉得这篇文章对你有帮助,别忘了点赞收藏哦~~