通过编写栈(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向前移动。
  • 链表实现栈:链表的每个节点存储一个元素,入栈时在链表头部插入节点,出栈时删除头节点。

过程解析

        分别使用数组和链表两种方式来实现栈,详细展示每种方式的代码。

数组实现栈

  1. 定义一个数组和一个变量 top 来存储栈顶的索引。
  2. 在 Push 操作中,增加 top 并插入元素。
  3. 在 Pop 操作中,减少 top 并返回栈顶元素。
  4. 在 Peek 中,直接访问 top 所指的元素。

链表实现栈

  1. 定义一个链表节点结构体。
  2. 使用链表的头节点作为栈顶,每次入栈时向链表头部插入节点。
  3. 出栈时删除链表头部节点,并更新下一个节点为栈顶。

示例代码

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; // 程序正常结束
}

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部