通过编写栈(Stack)数据结构,提升对基本数据结构的理解和运用。这也是掌握更复杂数据结构与算法的基础。栈是计算机科学中的一个重要概念,经常出现在许多算法和应用中。
栈(Stack)
栈是一种后进先出(LIFO, Last In First Out)的线性数据结构,类似于现实中的手枪的弹夹:最后放进去的子弹会第一个被射出来。这种结构在计算机科学中非常常见,广泛应用于递归、表达式求值、函数调用、深度优先搜索等算法中。
基本操作
栈有四个基本操作:
- Push(压栈/入栈):向栈中添加元素,通常是在栈顶位置添加。
- Pop(弹栈/出栈):移除栈顶元素,并返回该元素。这个操作遵循“后进先出”的原则,即最后入栈的元素会最先被弹出。
- Peek(查看栈顶元素):返回栈顶元素,但不移除它。这通常用于查看当前栈顶的内容。
- isEmpty(栈是否为空):检查栈中是否有元素。如果栈为空,返回 true;否则返回 false。
如下图所示:
特点
- 后进先出(LIFO):这是栈最重要的特性。最后压入的元素最先被弹出,最早压入的元素最后被处理。
- 受限操作:栈只允许从栈顶进行操作。即,插入和删除只能发生在栈顶,不能随机访问栈中间的元素。
- 空间限制:在一些实现中,栈的空间可能是有限的。比如用数组实现的栈,一旦数组的空间满了,就无法再插入新的元素。
实现方式
栈的实现方式主要有两种:数组实现和链表实现。
数组实现栈
在数组实现中,栈使用一个固定大小的数组来存储元素,且用一个变量 top 记录栈顶元素的索引位置。初始时 top 为 -1,表示栈为空。
- 优点:数组实现栈的结构简单,直接通过数组的索引操作栈,性能较好。
- 缺点:由于数组的大小是固定的,因此栈的容量有限,若元素过多,可能会导致栈溢出,不过也可以使用动态数组解决。
链表实现栈
在链表实现中,每个节点存储一个栈元素,并通过指针指向下一个节点。栈的栈顶为链表的头节点,每次入栈时在链表头部插入节点,出栈时删除头节点。
- 优点:链表栈没有容量限制,可以根据需要动态扩展栈的大小。
- 缺点:由于每个元素都需要动态分配内存,因此实现上比数组稍微复杂,而且需要手动管理内存。
题目要求
编写一个栈数据结构,支持以下操作:
- Push(x):将元素 x 压入栈中。
- Pop():从栈中移除并返回栈顶元素。
- Peek():返回栈顶元素,但不移除。
- isEmpty():检查栈是否为空。
进阶要求
- 使用数组或链表来实现栈(本文中用C实现数组栈,C++实现链表栈)。
- 当栈为空时,Pop 和 Peek 操作应当返回错误或给出相应提示。
做题思路
栈的操作基于“后进先出”(LIFO)的原则,即最后压入栈的元素最先被弹出。栈的两个核心操作是 Push(入栈)和 Pop(出栈)。为了实现栈,可以选择两种常见的数据结构:数组或链表。
- 数组实现栈:用一个数组来存储元素,定义数组的索引0为栈底,最后新添加的元素为栈顶。并使用一个索引位置top来标记栈顶位置。入栈时top向后移动,出栈时top向前移动。
- 链表实现栈:链表的每个节点存储一个元素,入栈时在链表头部插入节点,出栈时删除头节点。
过程解析
分别使用数组和链表两种方式来实现栈,详细展示每种方式的代码。
数组实现栈
- 定义一个数组和一个变量 top 来存储栈顶的索引。
- 在 Push 操作中,增加 top 并插入元素。
- 在 Pop 操作中,减少 top 并返回栈顶元素。
- 在 Peek 中,直接访问 top 所指的元素。
链表实现栈
- 定义一个链表节点结构体。
- 使用链表的头节点作为栈顶,每次入栈时向链表头部插入节点。
- 出栈时删除链表头部节点,并更新下一个节点为栈顶。
示例代码
C 实现:数组栈
#include <stdio.h>
#include <stdbool.h>// 引入布尔类型头文件,用于定义布尔类型变量(true/false)
#define MAX 100 // 定义栈的最大容量为100
// 栈结构体定义
typedef struct Stack {
int items[MAX]; // 定义数组用于存储栈元素
int top; // 记录栈顶的索引位置,初始为-1表示空栈,同时也表示栈内的数量
} Stack;
// 初始化栈函数,将栈的 top 初始化为 -1
void initStack(Stack* s) {
s->top = -1; // 栈顶设置为 -1,表示栈为空
}
// 检查栈是否为空
bool isEmpty(Stack* s) {
return s->top == -1; // 如果 top 为 -1,则栈为空,返回 true
}
// 检查栈是否已满
bool isFull(Stack* s) {
return s->top == MAX - 1; // 如果 top 达到 MAX-1,栈已满,返回 true
}
// 入栈操作
void push(Stack* s, int item) {
if (isFull(s)) { // 如果栈满,无法再插入元素
printf("栈满,无法入栈\n"); // 输出错误信息
return; // 直接返回
}
s->items[++(s->top)] = item; // 先将 top 加1,再将新元素存储到栈顶
printf("入栈元素: %d\n", item); // 打印入栈元素
}
// 出栈操作
int pop(Stack* s) {
if (isEmpty(s)) { // 如果栈为空,无法弹出元素
printf("栈为空,无法出栈\n"); // 输出错误信息
return -1; // 返回错误值 -1 表示操作失败
}
return s->items[(s->top)--]; // 返回栈顶元素,栈顶指针减1
}
// 获取栈顶元素但不移除
int peek(Stack* s) {
if (isEmpty(s)) { // 如果栈为空,无法获取栈顶元素
printf("栈为空,无法获取栈顶元素\n"); // 输出错误信息
return -1; // 返回错误值 -1 表示操作失败
}
return s->items[s->top]; // 返回栈顶的元素
}
int main()
{
Stack s; // 声明一个 Stack 类型的变量
initStack(&s); // 初始化栈
push(&s, 10); // 入栈元素 10
push(&s, 20); // 入栈元素 20
push(&s, 30); // 入栈元素 30
printf("栈顶元素: %d\n", peek(&s)); // 输出当前栈顶元素
printf("出栈元素: %d\n", pop(&s)); // 出栈,并输出弹出的元素
printf("栈顶元素: %d\n", peek(&s)); // 再次输出栈顶元素
return 0; // 程序正常结束
}
C++ 实现:链表栈
#include <iostream>
using namespace std;
// 定义链表节点结构体
struct Node {
int data; // 用于存储栈元素的值
Node* next; // 指向下一个节点的指针
};
// 定义栈类
class Stack {
private:
Node* top; // 栈顶指针,指向链表的头节点
int size; // 用于记录栈中元素的个数
public:
// 构造函数,初始化栈顶为 nullptr,表示栈为空,并且初始化元素个数为 0
Stack() {
top = nullptr; // 栈顶指针初始化为空
size = 0; // 初始时栈的元素个数为 0
}
// 检查栈是否为空
bool isEmpty() {
return top == nullptr; // 如果栈顶指针为 nullptr,说明栈为空
}
// 入栈操作
void push(int x) {
Node* newNode = new Node(); // 为新元素创建一个节点
newNode->data = x; // 将数据存储在新节点的 data 中
newNode->next = top; // 新节点的 next 指向当前的栈顶
top = newNode; // 更新栈顶指针为新节点
size++; // 栈中元素个数加 1
cout << "入栈元素: " << x << ",当前栈大小: " << size << endl; // 输出入栈的元素和当前栈大小
}
// 出栈操作
int pop() {
if (isEmpty()) { // 如果栈为空,无法执行出栈操作
cout << "栈为空,无法出栈" << endl; // 输出错误信息
return -1; // 返回 -1 表示出栈失败
}
Node* temp = top; // 临时保存当前栈顶节点
int poppedValue = temp->data; // 保存栈顶的值,用于返回
top = top->next; // 更新栈顶指针为下一个节点
delete temp; // 释放原栈顶节点的内存
size--; // 栈中元素个数减 1
cout << "出栈元素: " << poppedValue << ",当前栈大小: " << size << endl; // 输出弹出的元素和当前栈大小
return poppedValue; // 返回弹出的栈顶元素
}
// 查看栈顶元素
int peek() {
if (isEmpty()) { // 如果栈为空,无法查看栈顶元素
cout << "栈为空,无法获取栈顶元素" << endl; // 输出错误信息
return -1; // 返回 -1 表示操作失败
}
return top->data; // 返回栈顶的值
}
// 获取栈中元素的个数
int getSize() {
return size; // 返回栈的大小
}
// 析构函数,释放所有节点
~Stack() {
while (!isEmpty()) { // 只要栈不为空,就不断弹出栈顶元素
pop(); // 通过 pop 操作释放每个节点的内存
}
top = nullptr; // 在所有节点释放后,将栈顶指针显式设为 nullptr
}
};
int main()
{
Stack s; // 创建一个栈实例
s.push(10); // 入栈元素 10
s.push(20); // 入栈元素 20
s.push(30); // 入栈元素 30
cout << "栈顶元素: " << s.peek() << endl; // 输出当前栈顶元素
cout << "当前栈大小: " << s.getSize() << endl; // 输出当前栈的大小
cout << "出栈元素: " << s.pop() << endl; // 出栈,并输出弹出的元素
cout << "栈顶元素: " << s.peek() << endl; // 再次输出栈顶元素
cout << "当前栈大小: " << s.getSize() << endl; // 输出当前栈的大小
return 0; // 程序正常结束
}