数据结构核心:栈与队列的深度解析与实战实现

在数据结构的知识体系中,栈和队列是两种基础且高频使用的线性结构。它们看似简单,却承载着诸多核心算法思想,在函数调用、表达式求值、进程调度等场景中发挥着关键作用。本文将从概念本质出发,结合C语言与Java的代码实现,搭配经典应用案例与面试真题,帮你彻底吃透栈与队列。


目录

一、核心概念:从特性看透本质

1. 栈:后进先出(LIFO)的"单向通道"

2. 队列:先进先出(FIFO)的"排队通道"

二、底层实现:顺序存储与链式存储的取舍

1. 栈的两种实现方式

(1)顺序栈(数组实现)

(2)链式栈(链表实现)

2. 队列的两种实现方式

(1)链式队列(链表实现)

(2)循环队列(顺序实现优化)

三、经典应用场景:理论落地实战

1. 栈的核心应用

(1)括号匹配问题

(2)表达式求值(后缀表达式)

(3)函数调用栈

2. 队列的核心应用

(1)进程/任务调度

(2)广度优先搜索(BFS)

(3)缓冲区设计

四、面试高频真题:栈与队列的转换

1. 用栈实现队列

2. 用队列实现栈

五、总结与拓展


一、核心概念:从特性看透本质

栈和队列的核心区别在于数据存取的顺序规则,这一规则直接决定了它们的应用场景差异。二者均为线性结构,元素之间存在明确的前后逻辑关系,但限制了数据的操作方位。

1. 栈:后进先出(LIFO)的"单向通道"

栈的操作特性可概括为"后进先出"(Last In First Out),如同我们日常生活中的叠盘子——最后叠放的盘子,会被最先取出。栈只允许在一端(称为栈顶)进行数据的插入(压栈,Push)和删除(出栈,Pop)操作,另一端(栈底)则固定不动。

常见术语:

  • 栈顶:允许操作的一端,栈内元素变化时栈顶指针随之移动

  • 栈底:固定的一端,栈初始化后栈底位置不变

  • 空栈:栈内无元素时的状态

  • 栈满:栈内元素达到存储上限时的状态(仅顺序栈有此概念)

2. 队列:先进先出(FIFO)的"排队通道"

队列的操作特性为"先进先出"(First In First Out),类似银行柜台前的排队人群——先排队的人先办理业务。队列限制数据在一端(队尾)进行插入(入队,Enqueue),在另一端(队头)进行删除(出队,Dequeue)。

常见术语:

  • 队头:允许删除操作的一端

  • 队尾:允许插入操作的一端

  • 空队:队列中无元素的状态

  • 循环队列:为解决顺序队列"假溢出"问题而设计的优化结构,将存储空间视为环形

二、底层实现:顺序存储与链式存储的取舍

栈和队列的实现均有两种主流方式:基于数组的顺序存储和基于链表的链式存储。二者各有优劣,需根据实际场景选择。

1. 栈的两种实现方式

栈的核心操作是Push和Pop,无论哪种实现,都需保证操作仅在栈顶进行。

(1)顺序栈(数组实现)

顺序栈利用数组存储数据,通过一个栈顶指针(index)标记栈顶位置。初始化时栈顶指针为-1(表示空栈),Push时指针递增并赋值,Pop时指针递减。当栈顶指针等于数组长度-1时,栈满,需进行扩容。

C语言实现代码:

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

// 顺序栈结构体定义
typedef struct {
    int *data;   // 存储数据的数组
    int top;     // 栈顶指针(指向栈顶元素)
    int capacity;// 栈的容量
} SeqStack;

// 初始化栈
SeqStack* initStack(int initCapacity) {
    SeqStack *stack = (SeqStack*)malloc(sizeof(SeqStack));
    stack->data = (int*)malloc(sizeof(int) * initCapacity);
    stack->top = -1;          // 空栈标记
    stack->capacity = initCapacity;
    return stack;
}

// 栈扩容
void expandStack(SeqStack *stack) {
    int newCapacity = stack->capacity * 2;
    int *newData = (int*)realloc(stack->data, sizeof(int) * newCapacity);
    if (newData == NULL) {
        printf("扩容失败\n");
        exit(1);
    }
    stack->data = newData;
    stack->capacity = newCapacity;
}

// 压栈操作
void push(SeqStack *stack, int value) {
    // 栈满则扩容
    if (stack->top == stack->capacity - 1) {
        expandStack(stack);
        printf("栈扩容至%d\n", stack->capacity);
    }
    stack->data[++stack->top] = value; // 栈顶指针先增再赋值
}

// 出栈操作
int pop(SeqStack *stack) {
    if (stack->top == -1) {
        printf("栈为空,无法出栈\n");
        exit(1);
    }
    return stack->data[stack->top--]; // 先返回值再减指针
}

// 获取栈顶元素
int getTop(SeqStack *stack) {
    if (stack->top == -1) {
        printf("栈为空\n");
        exit(1);
    }
    return stack->data[stack->top];
}

// 判断栈是否为空
int isEmpty(SeqStack *stack) {
    return stack->top == -1;
}

// 销毁栈
void destroyStack(SeqStack *stack) {
    free(stack->data);
    free(stack);
}

// 测试代码
int main() {
    SeqStack *stack = initStack(2);
    push(stack, 10);
    push(stack, 20);
    push(stack, 30); // 触发扩容
    printf("栈顶元素:%d\n", getTop(stack)); // 30
    printf("出栈元素:%d\n", pop(stack));   // 30
    printf("栈顶元素:%d\n", getTop(stack)); // 20
    destroyStack(stack);
    return 0;
}

顺序栈优点:存取速度快(随机访问),实现简单;缺点:初始化时需预估容量,扩容有性能开销。

(2)链式栈(链表实现)

链式栈利用单链表存储数据,通常以链表头部作为栈顶(操作更高效),新元素插入头部(Push),删除头部元素(Pop)。无需考虑容量问题,实现动态扩容。

C语言实现代码:

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

// 链表节点结构体
typedef struct Node {
    int data;
    struct Node *next;
} Node;

// 链式栈结构体(仅需记录栈顶节点)
typedef struct {
    Node *top;
    int size; // 栈的大小(可选,方便统计)
} LinkStack;

// 初始化栈
LinkStack* initLinkStack() {
    LinkStack *stack = (LinkStack*)malloc(sizeof(LinkStack));
    stack->top = NULL;
    stack->size = 0;
    return stack;
}

// 压栈(头部插入)
void linkPush(LinkStack *stack, int value) {
    Node *newNode = (Node*)malloc(sizeof(Node));
    newNode->data = value;
    newNode->next = stack->top; // 新节点指向原栈顶
    stack->top = newNode;       // 栈顶更新为新节点
    stack->size++;
}

// 出栈(头部删除)
int linkPop(LinkStack *stack) {
    if (stack->top == NULL) {
        printf("栈为空,无法出栈\n");
        exit(1);
    }
    Node *temp = stack->top;    // 暂存栈顶节点
    int value = temp->data;     // 获取数据
    stack->top = temp->next;    // 栈顶指向后一个节点
    free(temp);                 // 释放节点内存
    stack->size--;
    return value;
}

// 销毁栈
void destroyLinkStack(LinkStack *stack) {
    Node *cur = stack->top;
    while (cur != NULL) {
        Node *temp = cur;
        cur = cur->next;
        free(temp);
    }
    free(stack);
}

// 测试
int main() {
    LinkStack *stack = initLinkStack();
    linkPush(stack, 10);
    linkPush(stack, 20);
    printf("出栈元素:%d\n", linkPop(stack)); // 20
    printf("栈大小:%d\n", stack->size);      // 1
    destroyLinkStack(stack);
    return 0;
}

链式栈优点:动态扩容,无需预估容量;缺点:存取速度略慢于顺序栈(需遍历指针),实现稍复杂。

2. 队列的两种实现方式

队列的核心是保证入队和出队的顺序性,链式实现更灵活,顺序实现需解决"假溢出"问题。

(1)链式队列(链表实现)

链式队列用单链表存储,需记录队头(用于出队)和队尾(用于入队)节点。入队时在队尾插入新节点,出队时删除队头节点,操作效率均为O(1)。

C实现代码:

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

// 链表节点结构体
typedef struct Node {
    int data;
    struct Node *next;
} Node;

// 链式队列结构体(记录队头和队尾)
typedef struct {
    Node *front; // 队头指针(指向队头元素)
    Node *rear;  // 队尾指针(指向队尾元素)
    int size;    // 队列大小,方便统计
} LinkQueue;

// 初始化队列
LinkQueue* initLinkQueue() {
    LinkQueue *queue = (LinkQueue*)malloc(sizeof(LinkQueue));
    queue->front = NULL;
    queue->rear = NULL;
    queue->size = 0;
    return queue;
}

// 入队操作(队尾插入)
void enqueue(LinkQueue *queue, int value) {
    Node *newNode = (Node*)malloc(sizeof(Node));
    newNode->data = value;
    newNode->next = NULL; // 新节点为队尾,next置空
    
    if (queue->front == NULL) { // 空队列时,队头队尾都指向新节点
        queue->front = newNode;
        queue->rear = newNode;
    } else {
        queue->rear->next = newNode; // 原队尾节点指向新节点
        queue->rear = newNode;      // 队尾指针更新为新节点
    }
    queue->size++;
}

// 出队操作(队头删除)
int dequeue(LinkQueue *queue) {
    if (queue->front == NULL) { // 队空判断
        printf("队列为空,无法出队\n");
        exit(1);
    }
    
    Node *temp = queue->front; // 暂存队头节点
    int value = temp->data;    // 获取队头数据
    
    // 若队列中只有一个元素,出队后队尾也置空
    if (queue->front == queue->rear) {
        queue->rear = NULL;
    }
    queue->front = queue->front->next; // 队头指针后移
    free(temp); // 释放原队头节点内存
    queue->size--;
    
    return value;
}

// 获取队头元素
int queuePeek(LinkQueue *queue) {
    if (queue->front == NULL) {
        printf("队列为空\n");
        exit(1);
    }
    return queue->front->data;
}

// 判断队列是否为空
int queueIsEmpty(LinkQueue *queue) {
    return queue->front == NULL;
}

// 获取队列大小
int queueSize(LinkQueue *queue) {
    return queue->size;
}

// 销毁队列
void destroyLinkQueue(LinkQueue *queue) {
    Node *cur = queue->front;
    while (cur != NULL) {
        Node *temp = cur;
        cur = cur->next;
        free(temp);
    }
    free(queue);
}

// 测试代码
int main() {
    LinkQueue *queue = initLinkQueue();
    enqueue(queue, 10);
    enqueue(queue, 20);
    printf("队头元素:%d\n", queuePeek(queue)); // 10
    printf("出队元素:%d\n", dequeue(queue));   // 10
    printf("队列大小:%d\n", queueSize(queue)); // 1
    destroyLinkQueue(queue);
    return 0;
}
(2)循环队列(顺序实现优化)

普通顺序队列中,队头出队后,前面的空间无法复用,会出现"假溢出"(数组未满但无法入队)。循环队列将数组视为环形,通过牺牲一个元素位置或使用计数器,判断队列空满状态。

核心逻辑:

  • 队空:front == rear

  • 队满:(rear + 1) % capacity == front(牺牲一个位置)

  • 入队:rear = (rear + 1) % capacity

  • 出队:front = (front + 1) % capacity

C实现代码:

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

// 循环队列结构体
typedef struct {
    int *data;    // 存储数据的数组
    int front;    // 队头指针(指向队头元素)
    int rear;     // 队尾指针(指向队尾元素的下一个位置)
    int capacity; // 队列的实际容量(物理数组大小)
} CircularQueue;

// 初始化循环队列
CircularQueue* initCircularQueue(int capacity) {
    // 牺牲一个位置,所以物理数组大小为capacity+1
    CircularQueue *queue = (CircularQueue*)malloc(sizeof(CircularQueue));
    queue->capacity = capacity + 1;
    queue->data = (int*)malloc(sizeof(int) * queue->capacity);
    queue->front = 0;
    queue->rear = 0;
    return queue;
}

// 入队操作
int cqEnqueue(CircularQueue *queue, int value) {
    // 判断队列是否已满
    if ((queue->rear + 1) % queue->capacity == queue->front) {
        printf("队列已满\n");
        return 0; // 入队失败返回0
    }
    queue->data[queue->rear] = value;
    queue->rear = (queue->rear + 1) % queue->capacity; // 环形移动
    return 1; // 入队成功返回1
}

// 出队操作
int cqDequeue(CircularQueue *queue) {
    // 判断队列是否为空
    if (queue->front == queue->rear) {
        printf("队列为空\n");
        exit(1);
    }
    int value = queue->data[queue->front];
    queue->front = (queue->front + 1) % queue->capacity; // 环形移动
    return value;
}

// 获取队头元素
int cqPeek(CircularQueue *queue) {
    if (queue->front == queue->rear) {
        printf("队列为空\n");
        exit(1);
    }
    return queue->data[queue->front];
}

// 判断队列是否为空
int cqIsEmpty(CircularQueue *queue) {
    return queue->front == queue->rear;
}

// 判断队列是否已满
int cqIsFull(CircularQueue *queue) {
    return (queue->rear + 1) % queue->capacity == queue->front;
}

// 销毁循环队列
void destroyCircularQueue(CircularQueue *queue) {
    free(queue->data);
    free(queue);
}

// 测试代码
int main() {
    // 初始化逻辑容量为2的循环队列(物理容量为3)
    CircularQueue *queue = initCircularQueue(2);
    cqEnqueue(queue, 10);
    cqEnqueue(queue, 20);
    printf("入队结果:%d\n", cqEnqueue(queue, 30)); // 队列已满,返回0
    printf("队头元素:%d\n", cqPeek(queue));       // 10
    printf("出队元素:%d\n", cqDequeue(queue));    // 10
    cqEnqueue(queue, 30); // 此时有空闲位置,可正常入队
    printf("新队头元素:%d\n", cqPeek(queue));     // 20
    destroyCircularQueue(queue);
    return 0;
}

三、经典应用场景:理论落地实战

栈和队列的应用贯穿编程开发与算法设计,掌握其典型场景是理解数据结构价值的关键。

1. 栈的核心应用

(1)括号匹配问题

问题描述:判断字符串中的括号(()、[]、{})是否匹配,如"({})"匹配,"({)}"不匹配。

解题思路:遍历字符串,遇到左括号则压栈;遇到右括号则弹出栈顶元素,判断是否为对应的左括号。遍历结束后,若栈为空则匹配成功。

C核心代码:

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

// 字符栈结构体(用于括号匹配)
typedef struct {
    char *data;
    int top;
    int capacity;
} CharStack;

// 初始化字符栈
CharStack* initCharStack(int initCapacity) {
    if (initCapacity <= 0) {
        initCapacity = 8; // 默认初始容量为8
    }
    CharStack *stack = (CharStack*)malloc(sizeof(CharStack));
    stack->data = (char*)malloc(sizeof(char) * initCapacity);
    stack->top = -1;
    stack->capacity = initCapacity;
    return stack;
}

// 字符栈扩容
void expandCharStack(CharStack *stack) {
    if (stack == NULL) return;
    int newCapacity = stack->capacity * 2;
    char *newData = (char*)realloc(stack->data, sizeof(char) * newCapacity);
    if (newData != NULL) {
        stack->data = newData;
        stack->capacity = newCapacity;
    } else {
        printf("字符栈扩容失败\n");
        exit(1);
    }
}

// 字符栈压栈
void charPush(CharStack *stack, char value) {
    if (stack == NULL) return;
    if (stack->top == stack->capacity - 1) {
        expandCharStack(stack);
    }
    stack->data[++stack->top] = value;
}

// 字符栈出栈(返回空字符表示栈空)
char charPop(CharStack *stack) {
    if (stack == NULL || stack->top == -1) {
        return '\0';
    }
    return stack->data[stack->top--];
}

// 判断字符栈是否为空
int charStackIsEmpty(CharStack *stack) {
    return stack != NULL && stack->top == -1;
}

// 销毁字符栈
void destroyCharStack(CharStack *stack) {
    if (stack != NULL) {
        free(stack->data);
        free(stack);
    }
}

// 括号匹配核心函数(返回1表示匹配成功,0表示失败)
int isValidBracket(const char *s) {
    if (s == NULL) return 0;
    int len = strlen(s);
    CharStack *stack = initCharStack(len);
    
    for (int i = 0; i < len; i++) {
        char c = s[i];
        // 左括号压栈
        if (c == '(' || c == '[' || c == '{') {
            charPush(stack, c);
        } else if (c == ')' || c == ']' || c == '}') {
            // 右括号,弹出栈顶匹配
            char topChar = charPop(stack);
            // 判断括号是否匹配
            if ((c == ')' && topChar != '(') ||
                (c == ']' && topChar != '[') ||
                (c == '}' && topChar != '{')) {
                destroyCharStack(stack);
                return 0;
            }
        }
        // 忽略非括号字符
    }
    
    // 遍历结束后栈需为空才是完全匹配
    int result = charStackIsEmpty(stack) ? 1 : 0;
    destroyCharStack(stack);
    return result;
}

// 测试代码
int main() {
    const char *s1 = "({})";
    const char *s2 = "({)}";
    const char *s3 = "{[()]}123";
    const char *s4 = "{{[[(())]]}}";
    
    printf("字符串\"%s\"括号匹配结果:%s\n", s1, isValidBracket(s1) ? "成功" : "失败");
    printf("字符串\"%s\"括号匹配结果:%s\n", s2, isValidBracket(s2) ? "成功" : "失败");
    printf("字符串\"%s\"括号匹配结果:%s\n", s3, isValidBracket(s3) ? "成功" : "失败");
    printf("字符串\"%s\"括号匹配结果:%s\n", s4, isValidBracket(s4) ? "成功" : "失败");
    
    return 0;
}
(2)表达式求值(后缀表达式)

问题描述:计算后缀表达式(逆波兰表达式)的值,如"3 4 + 5 *"的结果为35。

解题思路:遍历表达式,遇到数字压栈;遇到运算符则弹出两个栈顶元素,计算结果后压栈,最终栈顶元素即为结果。

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

// 数值栈结构体
typedef struct {
    int *data;
    int top;
    int capacity;
} NumStack;

// 初始化数值栈
NumStack* initNumStack(int initCapacity) {
    if (initCapacity <= 0) initCapacity = 8;
    NumStack *stack = (NumStack*)malloc(sizeof(NumStack));
    stack->data = (int*)malloc(sizeof(int) * initCapacity);
    stack->top = -1;
    stack->capacity = initCapacity;
    return stack;
}

// 数值栈扩容
void expandNumStack(NumStack *stack) {
    if (!stack) return;
    int newCap = stack->capacity * 2;
    int *newData = (int*)realloc(stack->data, sizeof(int) * newCap);
    if (newData) {
        stack->data = newData;
        stack->capacity = newCap;
    } else {
        printf("数值栈扩容失败\n");
        exit(1);
    }
}

// 数值栈压栈
void numPush(NumStack *stack, int val) {
    if (!stack) return;
    if (stack->top == stack->capacity - 1) expandNumStack(stack);
    stack->data[++stack->top] = val;
}

// 数值栈出栈
int numPop(NumStack *stack, int *val) {
    if (!stack || !val) return 0;
    if (stack->top == -1) {
        printf("数值栈为空\n");
        return 0;
    }
    *val = stack->data[stack->top--];
    return 1;
}

// 销毁数值栈
void destroyNumStack(NumStack *stack) {
    if (stack) {
        free(stack->data);
        free(stack);
    }
}

// 计算后缀表达式的值
int calculateRPN(const char *expr) {
    if (!expr || strlen(expr) == 0) {
        printf("表达式无效\n");
        exit(1);
    }
    
    NumStack *stack = initNumStack(8);
    int len = strlen(expr);
    char numBuf[20]; // 存储多位数
    int bufIdx = 0;
    
    for (int i = 0; i < len; i++) {
        char c = expr[i];
        if (c == ' ') {
            // 遇到空格,说明可能结束一个数字的读取
            if (bufIdx > 0) {
                numBuf[bufIdx] = '\0';
                int num = atoi(numBuf);
                numPush(stack, num);
                bufIdx = 0; // 重置缓冲区索引
            }
            continue;
        }
        
        // 遇到运算符,进行计算
        if (c == '+' || c == '-' || c == '*' || c == '/') {
            int b, a;
            // 先弹出右操作数,再弹出左操作数
            if (!numPop(stack, &b) || !numPop(stack, &a)) {
                destroyNumStack(stack);
                printf("表达式格式错误\n");
                exit(1);
            }
            
            int res;
            switch (c) {
                case '+': res = a + b; break;
                case '-': res = a - b; break;
                case '*': res = a * b; break;
                case '/': 
                    if (b == 0) {
                        destroyNumStack(stack);
                        printf("除数不能为0\n");
                        exit(1);
                    }
                    res = a / b; 
                    break;
                default: 
                    destroyNumStack(stack);
                    printf("未知运算符:%c\n", c);
                    exit(1);
            }
            numPush(stack, res);
        } 
        // 遇到数字或负号(负号需判断是否为数字开头)
        else if (isdigit(c) || (c == '-' && bufIdx == 0 && i + 1 < len && isdigit(expr[i+1]))) {
            numBuf[bufIdx++] = c;
        } else {
            destroyNumStack(stack);
            printf("表达式包含无效字符:%c\n", c);
            exit(1);
        }
    }
    
    // 处理表达式末尾的数字
    if (bufIdx > 0) {
        numBuf[bufIdx] = '\0';
        int num = atoi(numBuf);
        numPush(stack, num);
    }
    
    // 最终栈中应只有一个元素,即为结果
    int result;
    if (stack->top != 0 || !numPop(stack, &result)) {
        destroyNumStack(stack);
        printf("表达式格式错误\n");
        exit(1);
    }
    
    destroyNumStack(stack);
    return result;
}

// 测试代码
int main() {
    const char *expr1 = "3 4 + 5 *";
    const char *expr2 = "10 2 3 + * 6 -";
    const char *expr3 = "-5 3 * 2 -";
    
    printf("表达式\"%s\"结果:%d\n", expr1, calculateRPN(expr1)); // 35
    printf("表达式\"%s\"结果:%d\n", expr2, calculateRPN(expr2)); // 44
    printf("表达式\"%s\"结果:%d\n", expr3, calculateRPN(expr3)); // -17
    
    return 0;
}
(3)函数调用栈

编程语言的函数调用机制基于栈实现:主函数调用子函数时,主函数上下文(返回地址、局部变量)压栈;子函数执行完毕后,栈顶上下文弹出,恢复主函数执行。递归调用本质也是函数栈的不断压栈与弹栈。

2. 队列的核心应用

(1)进程/任务调度

操作系统的进程调度常采用队列结构,按"先进先出"原则分配CPU资源。例如,批处理系统中的作业队列,就绪状态的进程队列等。

(2)广度优先搜索(BFS)

BFS算法用于图或树的层次遍历,核心是用队列存储待访问节点:先入队根节点,然后依次出队节点,将其邻接节点入队,直至队空。

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

#define MAX_VERTEX 100 // 最大顶点数

// 邻接表节点结构体
typedef struct AdjNode {
    int vertex;           // 邻接顶点索引
    struct AdjNode *next; // 下一个邻接节点
} AdjNode;

// 顶点结构体
typedef struct Vertex {
    char data;         // 顶点数据
    AdjNode *firstAdj; // 第一个邻接节点
} Vertex;

// 图结构体(邻接表存储)
typedef struct {
    Vertex vertices[MAX_VERTEX]; // 顶点数组
    int vertexNum;               // 顶点数量
    int edgeNum;                 // 边数量
} Graph;

// 队列结构体(用于BFS)
typedef struct {
    int *data;
    int front;
    int rear;
    int capacity;
} BFSQueue;

// 初始化BFS队列
BFSQueue* initBFSQueue(int capacity) {
    BFSQueue *queue = (BFSQueue*)malloc(sizeof(BFSQueue));
    queue->data = (int*)malloc(sizeof(int) * capacity);
    queue->front = 0;
    queue->rear = 0;
    queue->capacity = capacity;
    return queue;
}

// BFS队列入队
int bfsEnqueue(BFSQueue *queue, int val) {
    if (!queue) return 0;
    if ((queue->rear + 1) % queue->capacity == queue->front) {
        printf("BFS队列已满\n");
        return 0;
    }
    queue->data[queue->rear] = val;
    queue->rear = (queue->rear + 1) % queue->capacity;
    return 1;
}

// BFS队列出队
int bfsDequeue(BFSQueue *queue, int *val) {
    if (!queue || !val) return 0;
    if (queue->front == queue->rear) {
        printf("BFS队列为空\n");
        return 0;
    }
    *val = queue->data[queue->front];
    queue->front = (queue->front + 1) % queue->capacity;
    return 1;
}

// 判断BFS队列是否为空
int bfsQueueIsEmpty(BFSQueue *queue) {
    return queue && queue->front == queue->rear;
}

// 销毁BFS队列
void destroyBFSQueue(BFSQueue *queue) {
    if (queue) {
        free(queue->data);
        free(queue);
    }
}

// 创建图
Graph* createGraph(int vertexNum) {
    if (vertexNum <= 0 || vertexNum > MAX_VERTEX) {
        printf("顶点数量无效\n");
        return NULL;
    }
    Graph *graph = (Graph*)malloc(sizeof(Graph));
    graph->vertexNum = vertexNum;
    graph->edgeNum = 0;
    
    // 初始化顶点
    for (int i = 0; i < vertexNum; i++) {
        printf("请输入第%d个顶点的数据:", i + 1);
        scanf(" %c", &graph->vertices[i].data);
        graph->vertices[i].firstAdj = NULL;
    }
    
    return graph;
}

// 添加无向边
void addUndirectedEdge(Graph *graph, int v1, int v2) {
    if (!graph || v1 < 0 || v1 >= graph->vertexNum || v2 < 0 || v2 >= graph->vertexNum) {
        printf("边添加失败,顶点索引无效\n");
        return;
    }
    
    // 创建v2到v1的邻接节点
    AdjNode *node1 = (AdjNode*)malloc(sizeof(AdjNode));
    node1->vertex = v2;
    node1->next = graph->vertices[v1].firstAdj;
    graph->vertices[v1].firstAdj = node1;
    
    // 创建v1到v2的邻接节点(无向边双向添加)
    AdjNode *node2 = (AdjNode*)malloc(sizeof(AdjNode));
    node2->vertex = v1;
    node2->next = graph->vertices[v2].firstAdj;
    graph->vertices[v2].firstAdj = node2;
    
    graph->edgeNum++;
}

// BFS遍历图
void BFS(Graph *graph, int startIdx) {
    if (!graph || startIdx < 0 || startIdx >= graph->vertexNum) {
        printf("BFS遍历失败,参数无效\n");
        return;
    }
    
    // 访问标记数组,0表示未访问,1表示已访问
    int *visited = (int*)calloc(graph->vertexNum, sizeof(int));
    BFSQueue *queue = initBFSQueue(graph->vertexNum);
    
    // 起始顶点入队并标记为已访问
    visited[startIdx] = 1;
    bfsEnqueue(queue, startIdx);
    printf("BFS遍历结果:");
    
    while (!bfsQueueIsEmpty(queue)) {
        int currIdx;
        bfsDequeue(queue, &currIdx);
        // 访问当前顶点
        printf('%c ', graph->vertices[currIdx].data);
        
        // 遍历当前顶点的所有邻接顶点
        AdjNode *currAdj = graph->vertices[currIdx].firstAdj;
        while (currAdj != NULL) {
            int adjIdx = currAdj->vertex;
            // 未访问的邻接顶点入队并标记
            if (!visited[adjIdx]) {
                visited[adjIdx] = 1;
                bfsEnqueue(queue, adjIdx);
            }
            currAdj = currAdj->next;
        }
    }
    printf("\n");
    
    free(visited);
    destroyBFSQueue(queue);
}

// 销毁图
void destroyGraph(Graph *graph) {
    if (!graph) return;
    
    // 释放邻接节点
    for (int i = 0; i < graph->vertexNum; i++) {
        AdjNode *curr = graph->vertices[i].firstAdj;
        while (curr != NULL) {
            AdjNode *temp = curr;
            curr = curr->next;
            free(temp);
        }
    }
    free(graph);
}

// 测试代码
int main() {
    // 创建含有5个顶点的图
    Graph *graph = createGraph(5);
    // 添加边:0-1, 0-2, 1-3, 1-4, 2-4
    addUndirectedEdge(graph, 0, 1);
    addUndirectedEdge(graph, 0, 2);
    addUndirectedEdge(graph, 1, 3);
    addUndirectedEdge(graph, 1, 4);
    addUndirectedEdge(graph, 2, 4);
    
    // 从顶点0开始BFS遍历
    BFS(graph, 0); // 遍历结果:假设顶点数据为A,B,C,D,E,则结果为A B C D E 
    
    destroyGraph(graph);
    return 0;
}
(3)缓冲区设计

键盘输入缓冲区、打印机输出缓冲区等,均采用队列结构。数据按产生顺序入队,按接收顺序出队,保证数据传输的有序性。

四、面试高频真题:栈与队列的转换

栈和队列的相互转换是面试中的经典问题,核心是利用两个栈/队列的组合,模拟目标结构的特性。

1. 用栈实现队列

思路:使用两个栈(入队栈inStack、出队栈outStack)。入队时直接压入inStack;出队时,若outStack为空,则将inStack所有元素弹出并压入outStack,再从outStack弹出元素(此时顺序已反转,符合队列特性)。

C实现代码:

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

// 栈结构体定义(用于实现队列)
typedef struct {
    int *data;
    int top;
    int capacity;
} Stack;

// 初始化栈
Stack* stackInit(int initCapacity) {
    if (initCapacity <= 0) initCapacity = 4;
    Stack *stack = (Stack*)malloc(sizeof(Stack));
    stack->data = (int*)malloc(sizeof(int) * initCapacity);
    stack->top = -1;
    stack->capacity = initCapacity;
    return stack;
}

// 栈扩容
void stackExpand(Stack *stack) {
    if (!stack) return;
    int newCap = stack->capacity * 2;
    int *newData = (int*)realloc(stack->data, sizeof(int) * newCap);
    if (newData) {
        stack->data = newData;
        stack->capacity = newCap;
    } else {
        printf("栈扩容失败\n");
        exit(1);
    }
}

// 栈压栈
void stackPush(Stack *stack, int val) {
    if (!stack) return;
    if (stack->top == stack->capacity - 1) stackExpand(stack);
    stack->data[++stack->top] = val;
}

// 栈出栈
int stackPop(Stack *stack, int *val) {
    if (!stack || !val) return 0;
    if (stack->top == -1) return 0;
    *val = stack->data[stack->top--];
    return 1;
}

// 判断栈是否为空
int stackIsEmpty(Stack *stack) {
    return stack && stack->top == -1;
}

// 销毁栈
void stackDestroy(Stack *stack) {
    if (stack) {
        free(stack->data);
        free(stack);
    }
}

// 用两个栈实现的队列结构体
typedef struct {
    Stack *inStack;  // 入队栈
    Stack *outStack; // 出队栈
} StackQueue;

// 初始化队列
StackQueue* sqInit() {
    StackQueue *queue = (StackQueue*)malloc(sizeof(StackQueue));
    queue->inStack = stackInit(4);
    queue->outStack = stackInit(4);
    return queue;
}

// 数据转移:将入队栈元素转移到出队栈
void sqTransfer(StackQueue *queue) {
    if (!queue || !stackIsEmpty(queue->outStack)) return;
    int val;
    while (stackPop(queue->inStack, &val)) {
        stackPush(queue->outStack, val);
    }
}

// 队列入队
void sqEnqueue(StackQueue *queue, int val) {
    if (!queue) return;
    stackPush(queue->inStack, val);
}

// 队列出队
int sqDequeue(StackQueue *queue, int *val) {
    if (!queue || !val) return 0;
    sqTransfer(queue);
    return stackPop(queue->outStack, val);
}

// 获取队头元素
int sqGetFront(StackQueue *queue, int *val) {
    if (!queue || !val) return 0;
    sqTransfer(queue);
    if (stackIsEmpty(queue->outStack)) return 0;
    *val = queue->outStack->data[queue->outStack->top];
    return 1;
}

// 判断队列是否为空
int sqIsEmpty(StackQueue *queue) {
    return queue && stackIsEmpty(queue->inStack) && stackIsEmpty(queue->outStack);
}

// 销毁队列
void sqDestroy(StackQueue *queue) {
    if (queue) {
        stackDestroy(queue->inStack);
        stackDestroy(queue->outStack);
        free(queue);
    }
}

// 测试代码
int main() {
    StackQueue *queue = sqInit();
    sqEnqueue(queue, 10);
    sqEnqueue(queue, 20);
    sqEnqueue(queue, 30);
    
    int frontVal;
    if (sqGetFront(queue, &frontVal)) {
        printf("队头元素:%d\n", frontVal); // 10
    }
    
    int deqVal;
    if (sqDequeue(queue, &deqVal)) {
        printf("出队元素:%d\n", deqVal); // 10
    }
    
    if (sqGetFront(queue, &frontVal)) {
        printf("队头元素:%d\n", frontVal); // 20
    }
    
    sqEnqueue(queue, 40);
    while (!sqIsEmpty(queue)) {
        sqDequeue(queue, &deqVal);
        printf("出队元素:%d\n", deqVal); // 20,30,40
    }
    
    sqDestroy(queue);
    return 0;
}

2. 用队列实现栈

思路:使用两个队列(主队列queue1、辅助队列queue2)。入栈时压入queue1;出栈时,将queue1中除最后一个元素外的所有元素转移到queue2,弹出queue1的最后一个元素,再将queue2元素转回queue1(或直接交换队列引用)。

C实现代码:

五、总结与拓展

栈和队列作为线性数据结构的"限制版",其价值在于通过约束操作规则,简化特定场景的问题解决。核心要点总结:

  • 特性区分:栈是LIFO,队列是FIFO,操作方位均受限制

  • 实现选择:顺序结构适合存取频繁场景,链式结构适合动态扩容场景

  • 核心应用:栈对应括号匹配、函数调用,队列对应调度、BFS

  • 面试重点:栈与队列的相互转换,需掌握两个容器的协同逻辑

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值