C/C++ 实现链式栈:带头结点与不带头结点的双方案讲解(附完整代码)
在数据结构中,栈(Stack)是一种“后进先出”(LIFO, Last In First Out)的线性表。相比顺序栈,链式栈具有不需要预分配固定大小、插入删除方便的优点,本文将详细介绍链式栈的概念,并对“带头结点”和“不带头结点”的两种实现方式进行分析和对比,帮助初学者掌握链式栈的实现思路。
一、链栈和链表的关系
链栈(LinkStack)是基于单链表实现的栈结构。也就是说,链栈其实就是用链表来模拟“栈”的行为,只不过我们只在链表头部进行插入(Push)和删除(Pop)操作。
- 在链表中,节点的插入/删除可以发生在任意位置;
- 而在链栈中,操作只能发生在栈顶(即链表头部);
- 因此,链栈是链表的一种特殊使用方式。
二、链式栈的两种实现方式
我们可以使用两种方式来实现链式栈:
- 不带头结点:栈顶指针
L
直接指向链表的第一个元素; - 带头结点:增加一个不存储数据的头结点,
L
指向该头结点,而真正的数据从L->next
开始。
三、不带头结点的链式栈实现(推荐用于理解栈本质)
✅ 特点:
- 栈顶指针
L
指向第一个实际数据节点; - 插入新元素使用“头插法”;
- 结构简单,易于理解。
💻 代码实现:
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
typedef struct LNode {
int data;
struct LNode* next;
} LNode, *LinkStack;
// 初始化栈
void init(LinkStack& L) {
L = NULL; // 栈为空
}
// 判断是否为空
bool Empty(LinkStack L) {
return L == NULL;
}
// 入栈操作(头插法)
bool Push(LinkStack& L, int e) {
LNode* s = (LNode*)malloc(sizeof(LNode));
s->data = e;
s->next = L;
L = s;
return true;
}
// 取栈顶元素
bool GetTop(LinkStack L, int& e) {
if (L == NULL) return false;
e = L->data;
return true;
}
// 出栈操作
bool Pop(LinkStack& L, int& x) {
if (L == NULL) return false;
LNode* p = L;
x = p->data;
L = p->next;
free(p);
return true;
}
int main() {
LinkStack L;
init(L);
int e;
printf("请输入入栈数据,以 -1 结束:\n");
while (scanf("%d", &e) && e != -1) {
Push(L, e);
}
if (GetTop(L, e)) {
printf("当前栈顶元素为:%d\n", e);
}
printf("依次出栈元素:\n");
while (Pop(L, e)) {
printf("%d 出栈成功\n", e);
}
return 0;
}
四、带头结点的链式栈实现(适合后续扩展)
✅ 特点:
- 多了一个“哨兵”节点,不存数据,仅用于统一操作逻辑;
L->next
指向真正的数据栈顶;- 在某些情况下便于操作,特别是栈空与非空判断时更统一。
💻 修改关键函数:
// 初始化(带头结点)
void init(LinkStack& L) {
L = (LNode*)malloc(sizeof(LNode));
L->next = NULL;
}
// 入栈
bool Push(LinkStack& L, int e) {
LNode* s = (LNode*)malloc(sizeof(LNode));
s->data = e;
s->next = L->next;
L->next = s;
return true;
}
// 获取栈顶元素
bool GetTop(LinkStack L, int& e) {
if (L->next == NULL) return false;
e = L->next->data;
return true;
}
// 出栈
bool Pop(LinkStack& L, int& x) {
if (L->next == NULL) return false;
LNode* p = L->next;
x = p->data;
L->next = p->next;
free(p);
return true;
}
五、两种方式的对比与选择建议
方面 | 不带头结点 | 带头结点 |
---|---|---|
内存开销 | 少一个头结点 | 多一个头结点 |
编程复杂度 | 略高(边界判断多) | 相对简单,逻辑统一 |
推荐用途 | 学习栈本质、栈顶即头结点 | 后续扩展或统一风格 |
👉 初学者建议从不带头结点开始掌握栈的核心逻辑,然后再理解带头结点的好处。
六、总结
- 链栈是通过链表实现的栈,具有动态分配、无栈满限制的优点;
- 常见的两种链栈实现方式是“带头结点”和“不带头结点”;
- 使用哪种方式可以根据需求和个人喜好选择;
- 掌握链栈,是为后续理解表达式求值、括号匹配、递归调用等打好基础!
如果你觉得本文对你有帮助,欢迎点赞、收藏、评论支持 👍
如有问题也欢迎留言讨论交流~