堆栈(Stack)是一种常用的数据结构,具有**后进先出(LIFO,Last In First Out)**的特性。在这篇博客中,我们将使用链表实现一个堆栈,深入剖析其代码结构与实现原理,帮助你掌握这一基础数据结构的动态实现方式。
本篇文章需要读者具有链表的基础知识,若不了解可参考以下文章:
一、堆栈的基本概念
1. 堆栈的特点
- 后进先出(LIFO): 最后压入栈的数据最先弹出。
- 两个主要操作:
push
(入栈):将元素压入堆栈。pop
(出栈):弹出栈顶元素。
2. 使用链表实现堆栈的优点
- 动态性: 链表可以动态分配内存,无需事先确定栈的大小。
- 灵活性: 与数组实现不同,不会因为固定大小限制栈的容量。
二、链表实现堆栈的设计
我们将使用一个单链表(Singly Linked List)来实现堆栈,其中:
- 每个节点(
Node
)包含一个值和指向下一个节点的指针。 - 栈顶(
top
)是链表的第一个节点,表示当前堆栈的栈顶元素。
1. 节点结构(Node
)
每个节点包含一个值和指向下一个节点的指针:
struct Node {
int value; // 节点的值
Node *next; // 指向下一个节点的指针
Node(int v) { // 构造函数
value = v;
next = nullptr;
}
};
2. 栈结构(Stack
)
- 栈顶指针(
top
):指向链表的第一个节点。 - 基本功能:
push(int x)
:将值x
入栈。pop()
:弹出栈顶元素。isEmpty()
:判断栈是否为空。- 析构函数:释放所有动态分配的节点,防止内存泄漏。
完整结构如下:
struct Stack {
private:
Node *top; // 栈顶指针
public:
Stack() {
top = nullptr; // 初始化为空栈
}
~Stack() {
while (top) {
Node *t = top;
top = top->next;
delete t;
}
}
bool isEmpty() {
return top == nullptr;
}
void push(int x) {
Node *t = new Node(x);
t->next = top;
top = t;
}
int pop() {
if (isEmpty()) {
throw runtime_error("Stack is empty!");
}
Node *t = top;
top = top->next;
int res = t->value;
delete t;
return res;
}
};
三、完整代码示例
以下是完整代码,包括堆栈的定义及其在主函数中的使用:
#include "bits/stdc++.h"
using namespace std;
struct Node {
int value;
Node *next;
Node(int v) {
value = v;
next = nullptr;
}
};
struct Stack {
private:
Node *top;
public:
Stack() {
top = nullptr;
}
~Stack() {
while (top) {
Node *t = top;
top = top->next;
delete t;
}
}
bool isEmpty() {
return top == nullptr;
}
void push(int x) {
Node *t = new Node(x);
t->next = top;
top = t;
}
int pop() {
if (isEmpty()) {
throw runtime_error("Stack is empty!");
}
Node *t = top;
top = top->next;
int res = t->value;
delete t;
return res;
}
};
int main() {
Stack *s = new Stack();
s->push(1);
s->push(2);
s->push(3);
cout << s->pop() << endl; // 输出 3
cout << s->pop() << endl; // 输出 2
cout << s->pop() << endl; // 输出 1
delete s;
return 0;
}
输出结果
3
2
1
四、关键点解析
1. 动态内存管理
- 在
push
操作中,动态分配内存用于存储新节点。 - 在
pop
操作中,释放已弹出节点的内存。
2. 异常处理
当栈为空时,pop
会抛出异常,防止非法操作:
if (isEmpty()) {
throw runtime_error("Stack is empty!");
}
3. 析构函数
使用 ~Stack()
释放所有动态分配的节点,避免内存泄漏:
~Stack() {
while (top) {
Node *t = top;
top = top->next;
delete t;
}
}
五、总结
通过这篇文章,我们实现了一个基于链表的堆栈,并学习了以下知识:
- 如何使用链表动态实现堆栈的基本操作。
- 如何合理管理内存,避免内存泄漏。
- 异常处理的重要性,以及如何通过析构函数清理资源。
这种动态实现方式非常适合需要灵活调整大小的场景,希望本文能帮助你更好地理解堆栈这一数据结构。