文章目录
- 栈与队列
- 栈
- 顺序栈
- 核心操作
- 顺序共享栈
- 核心操作
- 链栈
- 核心操作
- 栈的应用
- 常见应用场景
- 表达式求值详解
- 队列
- 顺序队列
- 核心操作
- 循环队列
- 核心操作
- 关键计算
- 链式队列
- 核心操作
- 队列的应用
- 常见应用场景
- 栈与队列的区别
栈与队列
栈和队列是两种操作受限制的线性表数据结构,它们在计算机科学中应用广泛。理解它们的特性和实现原理是掌握数据结构的基础。
栈
**LIFO(后进先出)**原则:最后进入的元素最先被移除
顺序栈
动态演示
顺序栈使用数组实现栈结构:
typedef struct{ Elemtype data[Maxsize]; int top; // 栈顶指针
}SqStack;
核心操作
- 栈空(初始化):
s->top == -1
- 栈满:
s->top == Maxsize-1
- 进栈:指针先加1,再放入元素
- 出栈:先取出元素,指针再减1
重要特性:
- 入栈出栈时间复杂度均为 O(1)
- n个元素的合法出栈序列个数为卡特兰数:
C n = 1 n + 1 ( 2 n n ) C_n = \frac{1}{n+1}\binom{2n}{n} Cn=n+11(n2n)
例如 n=4 时,有14种合法序列
注意:某些实现可能让栈空时
top==0
,此时:
- 入栈:先放入元素,指针再加1
- 出栈:指针先减1,再取出元素
顺序共享栈
为节省空间,两个栈共享同一数组空间
typedef struct{ Elemtype data[Maxsize]; int top1, top2; // top1从0开始,top2从Maxsize-1开始
}SqStack;
核心操作
- 栈空:
- 栈1空:
top1 == -1
- 栈2空:
top2 == Maxsize
- 栈1空:
- 栈满:
top1 == top2 - 1
- 进栈:先动指针,再放入元素
- 出栈:先取出元素,再动指针
链栈
动态演示
使用链表实现的栈结构:
typedef struct linknode{ Elemtype data; struct linknode *next;
}LinkStNode;
核心操作
- 栈空(初始化):
s->next == NULL
- 栈满:理论上不会栈满
- 进栈:使用头插法添加新节点
- 出栈:取出头节点数据并删除节点
p = head->next; e = p->data; head->next = p->next; free(p);
特点:栈底节点的
next
指针为NULL
栈的应用
常见应用场景
- 括号匹配:检查表达式中的括号是否成对出现
- 表达式求值:将中缀表达式转换为后缀/前缀表达式
- 函数调用栈:实现递归调用和局部变量存储
- 进制转换:十进制转二进制/八进制/十六进制
- 迷宫求解:记录路径和回溯点
表达式求值详解
- 前缀表达式(波兰式):运算符在操作数之前
+ 1 * 2 3
- 中缀表达式:运算符在操作数之间
1 + 2 * 3
- 后缀表达式(逆波兰式):运算符在操作数之后
1 2 3 * +
转换规则:
- 操作数相对位置不变
- 运算符位置根据就近原则调整
- 后缀表达式从左到右入栈计算
- 前缀表达式从右到左入栈计算
示例:中缀
1+2*3
转换:
- 前缀:
+ 1 * 2 3
- 后缀:
1 2 3 * +
队列
**FIFO(先进先出)**原则:最先进入的元素最先被移除
尾进头出:入队操作在尾部,出队操作在头部
顺序队列
动态演示
typedef struct{ elemtype data[Maxsize]; int rear, front; // 尾指针和头指针
}SqQueue;
核心操作
- 初始化:
q->rear = q->front = -1
- 队空:
q->front == q->rear
- 队满:
q->rear == Maxsize-1
- 入队:
++rear
后放入元素 - 出队:取出元素后
++front
注意:
front
总是指向队首元素的前一个位置- 含n个空间的队列最多执行n-1次入队操作
- 入队出队时间复杂度均为 O(1)
循环队列
解决顺序队列的"假溢出"问题
typedef struct{ elemtype data[Maxsize]; int rear, front;
}SqQueue;
核心操作
- 初始化:
q->front = q->rear = 0
- 队空:
q->rear == q->front
- 队满:
(q->rear + 1) % Maxsize == q->front
(浪费一个空间区分空/满) - 入队:
q->rear = (q->rear + 1) % Maxsize
- 出队:
front = (front + 1) % Maxsize
关键计算
- 元素个数:
count = (rear - front + Maxsize) % Maxsize
- 队头位置:
front = (rear - count + Maxsize) % Maxsize
- 队尾位置:
rear = (front + count) % Maxsize
(仅当有减号时需加Maxsize)
链式队列
typedef struct qnode{ elemtype data; struct qnode *next;
}Datanode;typedef struct{ Datanode *front; Datanode *rear;
}Linkqunode;
核心操作
- 初始化:
q->front = q->rear = NULL
- 队空:
q->front == NULL
或q->rear == NULL
- 入队:在尾部插入新节点(空队列需特殊处理)
- 出队:删除头节点(只有一个节点需特殊处理)
- 判断单节点:
q->front == q->rear != NULL
队列的应用
常见应用场景
- 缓冲区管理:操作系统中的I/O缓冲区
- CPU调度:进程就绪队列
- 打印队列:管理打印任务
- 广度优先搜索:图的层次遍历
- 页面替换算法:操作系统内存管理
- 迷宫求解:记录待探索的路径
栈与队列的区别
特性 | 栈 | 队列 |
---|---|---|
操作原则 | LIFO(后进先出) | FIFO(先进先出) |
结构特点 | 仅需维护栈顶指针 | 需维护头尾双指针 |
入操作 | 先动指针,再放入元素 | 移动rear指针后放元素 |
出操作 | 先取元素,再动指针 | 移动front指针后取元素 |
典型应用 | 函数调用、表达式求值 | 任务调度、缓冲区管理 |
空间特性 | 顺序栈可能浪费空间 | 循环队列解决空间浪费 |