数据结构—栈和队列 - 实践

栈和队列都是线性表,核心区别在于元素的 “进出顺序”,以下是具体解析:

一、栈(Stack)

核心结论:栈是 “先进后出(LIFO,Last In First Out)” 的数据结构,仅允许在一端(栈顶)进行插入和删除操作。

关键特性
  1. 操作限制:插入(push)和删除(pop)仅在栈顶执行,栈底固定。
  2. 访问特性:只能访问栈顶元素,无法直接访问中间元素。
常见实现与应用
  • 实现方式:数组栈(顺序存储)、链表栈(链式存储)。
  • 典型应用:函数调用栈、表达式求值、括号匹配、回溯算法(如迷宫求解)。

一、栈(数组实现,顺序栈)

1. 结构定义与头文件

#include 
#include 
#define MAX_SIZE 100  // 栈的最大容量
// 栈结构定义
typedef struct {
    int data[MAX_SIZE];  // 存储栈元素
    int top;             // 栈顶指针(-1表示空栈)
} Stack;

2. 核心操作实现

(1)初始化栈
void initStack(Stack* stack) {
    stack->top = -1;  // 空栈标识
}
(2)判断栈空
int isStackEmpty(Stack* stack) {
    return stack->top == -1;
}
(3)判断栈满
int isStackFull(Stack* stack) {
    return stack->top == MAX_SIZE - 1;
}
(4)入栈(push)
int push(Stack* stack, int value) {
    if (isStackFull(stack)) {
        printf("栈满,入栈失败\n");
        return 0;
    }
    stack->data[++stack->top] = value;  // 栈顶指针上移,存入元素
    return 1;
}
(5)出栈(pop)
int pop(Stack* stack, int* value) {
    if (isStackEmpty(stack)) {
        printf("栈空,出栈失败\n");
        return 0;
    }
    *value = stack->data[stack->top--];  // 取出栈顶元素,栈顶指针下移
    return 1;
}
(6)取栈顶元素
int getTop(Stack* stack, int* value) {
    if (isStackEmpty(stack)) {
        printf("栈空,无栈顶元素\n");
        return 0;
    }
    *value = stack->data[stack->top];  // 仅读取栈顶元素,不改变栈结构
    return 1;
}

3. 测试示例

int main() {
    Stack stack;
    initStack(&stack);
    int val;
    push(&stack, 10);
    push(&stack, 20);
    push(&stack, 30);
    getTop(&stack, &val);
    printf("栈顶元素:%d\n", val);  // 输出:30
    pop(&stack, &val);
    printf("出栈元素:%d\n", val);  // 输出:30
    getTop(&stack, &val);
    printf("栈顶元素:%d\n", val);  // 输出:20
    return 0;
}

使用栈计算表达式的值

概述
通过两个栈(数值栈和符号栈)实现中缀表达式求值。算法核心是:

遇到数字时,累加并入数值栈;
遇到运算符时,比较其与符号栈顶运算符的优先级:
若当前运算符优先级更高,则直接入栈;
否则,不断弹出符号栈顶运算符与两个数值进行计算,结果重新压入数值栈,直到满足入栈条件。
表达式结束后,处理剩余符号栈中的运算符。
目标表达式示例:20*3+5,预期结果为 65。

栈结构定义(链式栈
#ifndef _LINKSTACK_H_
#define _LINKSTACK_H_
// 定义栈中存储的数据类型,可存放数字或字符(运算符)
typedef struct
{
   int num;     // 用于存储操作数
   char sym;    // 用于存储运算符
} DATATYPE;
// 链栈节点结构
typedef struct _linkstacknode
{
    DATATYPE data;                    // 当前节点数据
    struct _linkstacknode *next;      // 指向下一个节点
} LinkStackNode;
// 链栈整体结构
typedef struct
{
    LinkStackNode* top;   // 栈顶指针
    int clen;             // 当前栈中元素个数
} LinkStack;
// 函数声明
LinkStack* CreateLinkStack();                    // 创建空栈
int PushLinkStack(LinkStack* ls, DATATYPE* data); // 元素入栈
int PopLinkStack(LinkStack* ls);                 // 栈顶元素出栈
DATATYPE* GetTopLinkStack(LinkStack* ls);        // 获取栈顶元素(不弹出)
int IsEmptyLinkStack(LinkStack* ls);             // 判断栈是否为空
int GetSizeLinkStack(LinkStack* ls);             // 获取栈中元素个数
int DestroyLinkStack(LinkStack*);                // 销毁整个栈(释放内存)
#endif // !_LINKSTACK_H_

说明:该头文件定义了一个通用链式栈,支持存储整数和字符类型数据,适用于数值栈和符号栈。

链栈实现(LinkStack.c)
#include "LinkStack.h"
#include 
#include 
#include 
/**
 * 创建一个新的链栈
 * @return 成功返回栈指针,失败返回 NULL
 */
LinkStack* CreateLinkStack()
{
    LinkStack* ls = malloc(sizeof(LinkStack));  // 分配栈控制块内存
    if (NULL == ls)
    {
        perror("CreateLinkStack malloc error\n");
        return NULL;
    }
    ls->top = NULL;   // 初始化栈顶为空
    ls->clen = 0;     // 初始元素个数为 0
    return ls;
}
/**
 * 元素入栈(头插法)
 * @param ls 待操作的栈
 * @param data 要入栈的数据(指针)
 * @return 0 成功,非 0 失败
 */
int PushLinkStack(LinkStack* ls, DATATYPE* data)
{
    LinkStackNode* newnode = malloc(sizeof(LinkStackNode));
    if (NULL == newnode)
    {
        perror("PushLinkStack malloc error\n");
        return 1;
    }
    memcpy(&newnode->data, data, sizeof(DATATYPE));  // 复制数据
    newnode->next = ls->top;                         // 新节点指向原栈顶
    ls->top = newnode;                               // 更新栈顶
    ls->clen++;                                      // 元素个数加一
    return 0;
}
/**
 * 出栈操作(头删法)
 * @param ls 待操作的栈
 * @return 0 成功,非 0 失败(栈空)
 */
int PopLinkStack(LinkStack* ls)
{
    if (IsEmptyLinkStack(ls))
    {
        printf("linkstack is empty\n");
        return 1;
    }
    LinkStackNode* tmp = ls->top;         // 临时保存栈顶节点
    ls->top = ls->top->next;              // 栈顶下移
    free(tmp);                            // 释放原栈顶节点
    ls->clen--;                           // 元素个数减一
    return 0;
}
/**
 * 获取栈顶元素(不弹出)
 * @param ls 待操作的栈
 * @return 指向栈顶数据的指针,栈空时返回 NULL
 */
DATATYPE* GetTopLinkStack(LinkStack* ls)
{
    if (IsEmptyLinkStack(ls))
    {
        return NULL;
    }
    return &ls->top->data;  // 返回栈顶数据地址
}
/**
 * 判断栈是否为空
 * @param ls 待检查的栈
 * @return 1 表示空,0 表示非空
 */
int IsEmptyLinkStack(LinkStack* ls)
{
    return 0 == ls->clen;
}
/**
 * 获取栈中元素个数
 * @param ls 待查询的栈
 * @return 元素个数
 */
int GetSizeLinkStack(LinkStack* ls)
{
    return ls->clen;
}
/**
 * 销毁整个链栈(释放所有节点及控制块)
 * @param ls 要销毁的栈
 * @return 0(固定返回值)
 */
int DestroyLinkStack(LinkStack* ls)
{
    while (!IsEmptyLinkStack(ls))
    {
        PopLinkStack(ls);  // 循环出栈,自动释放节点
    }
    free(ls);  // 释放栈控制块
    return 0;
}
表达式求值主程序(main.c)
#include 
#include 
#include "LinkStack.h"
int num = 0;  // 用于临时存储正在解析的数字
/**
 * 将字符数字累加到全局变量 num 中
 * @param c 当前字符('0'-'9')
 */
void get_num(char c)
{
    num = num * 10 + c - '0';  // 构造多位整数
}
/**
 * 获取运算符优先级
 * @param c 运算符字符
 * @return 优先级:+,- 为 1;*,/ 为 2;其他为 0
 */
int get_priority(char c)
{
    switch (c)
    {
        case '+':
        case '-':
            return 1;
        case '*':
        case '/':
            return 2;
        default:
            return 0;
    }
}
/**
 * 执行两个数之间的基本运算
 * @param num1 第一个操作数
 * @param num2 第二个操作数
 * @param c 运算符
 * @return 计算结果
 */
int get_result(int num1, int num2, char c)
{
    switch (c)
    {
        case '+':
            return num1 + num2;
        case '-':
            return num1 - num2;
        case '*':
            return num1 * num2;
        case '/':
            return num1 / num2;
    }
    return 0;  // 默认返回值(理论上不会执行)
}
/**
 * 主函数:计算中缀表达式 "20*3+5"
 */
int main(int argc, char** argv)
{
    char* express = "20*3+5";                    // 输入表达式
    LinkStack* ls_num = CreateLinkStack();       // 数值栈
    LinkStack* ls_sym = CreateLinkStack();       // 符号栈
    char* tmp = express;                         // 遍历指针
    DATATYPE* top;                               // 临时指针
    DATATYPE data;                               // 临时数据变量
    while (*tmp)
    {
        bzero(&data, sizeof(data));  // 清空临时数据
        // 处理数字字符
        if (*tmp >= '0' && *tmp <= '9')
        {
            get_num(*tmp);
            tmp++;
            continue;
        }
        // 遇到运算符前,将已解析的数字压入数值栈
        data.num = num;
        num = 0;
        PushLinkStack(ls_num, &data);
        // 处理当前运算符,与符号栈顶比较优先级
        while (1)
        {
            top = GetTopLinkStack(ls_sym);
            // 条件1:符号栈为空,直接入栈
            // 条件2:当前运算符优先级高于栈顶,直接入栈
            if (IsEmptyLinkStack(ls_sym) ||
                (top != NULL && get_priority(top->sym) < get_priority(*tmp)))
            {
                bzero(&data, sizeof(data));
                data.sym = *tmp;
                PushLinkStack(ls_sym, &data);
                break;
            }
            else
            {
                // 否则:弹出两个数值和一个运算符进行计算
                top = GetTopLinkStack(ls_num);
                int num2 = top->num;
                PopLinkStack(ls_num);
                top = GetTopLinkStack(ls_num);
                int num1 = top->num;
                PopLinkStack(ls_num);
                top = GetTopLinkStack(ls_sym);
                char op = top->sym;
                PopLinkStack(ls_sym);
                int result = get_result(num1, num2, op);
                bzero(&data, sizeof(data));
                data.num = result;
                PushLinkStack(ls_num, &data);  // 结果压回数值栈
            }
        }
        tmp++;
    }
    // 处理最后一个数字(循环外)
    data.num = num;
    num = 0;
    PushLinkStack(ls_num, &data);
    // 处理剩余符号栈中的运算符(从左到右)
    while (!IsEmptyLinkStack(ls_sym))
    {
        top = GetTopLinkStack(ls_num);
        int num2 = top->num;
        PopLinkStack(ls_num);
        top = GetTopLinkStack(ls_num);
        int num1 = top->num;
        PopLinkStack(ls_num);
        top = GetTopLinkStack(ls_sym);
        char op = top->sym;
        PopLinkStack(ls_sym);
        int result = get_result(num1, num2, op);
        bzero(&data, sizeof(data));
        data.num = result;
        PushLinkStack(ls_num, &data);
    }
    // 最终结果在数值栈顶
    top = GetTopLinkStack(ls_num);
    int result = top->num;
    PopLinkStack(ls_num);
    printf("result %d\n", result);  // 输出结果
    // 释放资源
    DestroyLinkStack(ls_num);
    DestroyLinkStack(ls_sym);
    return 0;
}

理想运行结果

result 65

说明:表达式 20*3+5 按照优先级先算乘法 20*3=60,再加 5,最终得 65

二、队列(Queue)

核心结论:队列是 “先进先出(FIFO,First In First Out)” 的数据结构,元素从一端(队尾)插入,从另一端(队头)删除。

作用:做缓冲区(速度不匹配),控制速度
2.特点:①顺序表,循环表
           循环方法:用长度取余%
           ②队空的条件:头和尾的位置重合(tail==head)
           ③队满的条件:尾的位置加一等于头的位置(tail+1=head)
          ④允许增加的是队尾,允许删除的一端是队头
关键特性
  1. 操作限制:插入(enqueue)在队尾,删除(dequeue)在队头,两端各司其职。
  2. 访问特性:只能访问队头元素,无法直接访问中间元素。
  3. 主要类型
    • 顺序队列
    • 循环队列(避免假溢出)

队列(循环队列,数组实现):循环队列,解决假溢出:利用取模运算(循环移动)

循环队列结构定义(seqque.h)
#ifndef __SEQQUE__H__
#define __SEQQUE__H__
typedef int DATATYPE;  // 数据类型别名
/**
 * 循环队列结构体
 * array: 存储数据的数组
 * head:  队头索引(指向第一个元素)
 * tail:  队尾“下一个空位”索引
 * tlen:  数组总长度
 */
typedef struct
{
    DATATYPE* array;
    int head;
    int tail;
    int tlen;
} SeqQue;
// 函数声明
SeqQue* CreateSeqQue(int len);               // 创建队列
int EnterSeqQue(SeqQue* sq, DATATYPE* data); // 入队
int QuitSeqQue(SeqQue* sq);                  // 出队
DATATYPE* GetHeadSeqQue(SeqQue* sq);         // 获取队头元素
int IsEmptySeqQue(SeqQue* sq);               // 判断空
int IsFullSeqQue(SeqQue* sq);                // 判断满
int DestroySeqQue(SeqQue* sq);               // 销毁队列
#endif  // !__SEQQUE__H__
循环队列实现(seqque.c)
#include "seqque.h"
#include 
#include 
#include 
/**
 * 创建长度为 len 的循环队列
 * @param len 队列容量(实际可存 len-1 个元素)
 * @return 成功返回队列指针,失败返回 NULL
 */
SeqQue* CreateSeqQue(int len)
{
    SeqQue* sq = malloc(sizeof(SeqQue));
    if (NULL == sq)
    {
        perror("CreateSeqQue malloc");
        return NULL;
    }
    sq->array = malloc(sizeof(DATATYPE) * len);
    if (NULL == sq->array)
    {
        perror("CreateSeqQue malloc2");
        free(sq);
        return NULL;
    }
    sq->head = 0;
    sq->tail = 0;
    sq->tlen = len;
    return sq;
}
/**
 * 元素入队(队尾)
 * @param sq 队列
 * @param data 要插入的数据
 * @return 0 成功,1 失败(队满)
 */
int EnterSeqQue(SeqQue* sq, DATATYPE* data)
{
    if (IsFullSeqQue(sq))
    {
        printf("queue is full\n");
        return 1;
    }
    memcpy(&sq->array[sq->tail], data, sizeof(DATATYPE));
    sq->tail = (sq->tail + 1) % sq->tlen;  // 循环移动
    return 0;
}
/**
 * 元素出队(队头)
 * @param sq 队列
 * @return 0 成功,1 失败(队空)
 */
int QuitSeqQue(SeqQue* sq)
{
    if (IsEmptySeqQue(sq))
    {
        printf("queue is empty\n");
        return 1;
    }
    sq->head = (sq->head + 1) % sq->tlen;  // 循环移动
    return 0;
}
/**
 * 获取队头元素地址(不删除)
 * @param sq 队列
 * @return 指向队头元素的指针
 */
DATATYPE* GetHeadSeqQue(SeqQue* sq)
{
    return &sq->array[sq->head];
}
/**
 * 判断队列是否为空
 * @param sq 队列
 * @return 1 为空,0 非空
 */
int IsEmptySeqQue(SeqQue* sq)
{
    return sq->head == sq->tail;
}
/**
 * 判断队列是否为满
 * @param sq 队列
 * @return 1 为满,0 非满
 */
int IsFullSeqQue(SeqQue* sq)
{
    return (sq->tail + 1) % sq->tlen == sq->head;
}
/**
 * 销毁队列(释放内存)
 * @param sq 队列
 * @return 0
 */
int DestroySeqQue(SeqQue* sq)
{
    free(sq->array);
    free(sq);
    return 0;
}
队列测试程序(main.c)
#include 
#include "seqque.h"
int main(int argc, char** argv)
{
    SeqQue* sq = CreateSeqQue(10);  // 创建容量为 10 的队列(最多存 9 个)
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        EnterSeqQue(sq, &i);  // 0~9 依次入队
    }
    // 第10次入队会失败,打印 "queue is full"
    i = 0;
    while (!IsEmptySeqQue(sq))
    {
        DATATYPE* tmp = GetHeadSeqQue(sq);
        printf("%d %d\n", i++, *tmp);  // 打印序号和值
        QuitSeqQue(sq);                // 出队
    }
    // 实际输出 0~8,共 9 个数(因队列满,最后一个未入)
    DestroySeqQue(sq);
    return 0;
}

理想运行结果

0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8

说明:循环队列容量为 10,但最多只能存 9 个元素(tail+1 == head 判满),因此 i=9 时入队失败。

常见实现与应用
  • 顺序队列(数组):存在 “假溢出” 问题,需用循环队列优化。
  • 循环队列(数组):通过取模运算实现指针循环,提高空间利用率。
  • 链队(链表):动态扩容,无队满问题,适合元素数量不确定场景。
  • 典型应用:进程调度、任务队列、消息队列、缓冲处理(如打印机队列)。

三、核心区别对比

维度栈(Stack)队列(Queue)
进出顺序先进后出(LIFO)先进先出(FIFO)
操作端仅栈顶一端队头(删除)、队尾(插入)两端
核心操作push(入栈)、pop(出栈)enqueue(入队)、dequeue(出队)
访问限制仅访问栈顶元素仅访问队头元素

四、特殊扩展

  • 双端队列(Deque):允许在两端进行插入和删除,兼具栈和队列的特性。
  • 优先级队列:元素带优先级,出队时按优先级排序(非严格 FIFO,底层常用堆实现)。
posted @ 2026-01-28 13:46  yangykaifa  阅读(0)  评论(0)    收藏  举报