17-C++ 数据结构 - 栈

1.1 什么是栈

栈是一种线性数据结构,具有后进先出(Last-In-First-Out,LIFO)的特点。可以类比为装满盘子的餐桌,每次放盘子都放在最上面,取盘子时也从最上面取,因此最后放进去的盘子最先取出。栈的典型应用场景有函数调用、括号匹配、表达式求值等。

元素从固定一侧开口进入和离开栈的操作,分别称为入栈和出栈。

栈中元素由深到浅存储,我们将栈最深的存储位置称为栈底,当前栈中最外围元素所在的位置则称为栈顶。

17-C++ 数据结构 - 栈_第1张图片

栈底通常固定不变,而栈顶则由当前栈中最外侧元素位置(栈中元素数量)确定。

由栈的特点可知栈的插入和删除(入栈和出栈)操作都是在栈顶这一端进行的。

栈顶(Top):线性表允许进行插入删除的那一端。
栈底(Bottom):固定的,不允许进行插入和删除的另一端。
空栈:不含任何元素的空表。

栈又称为后进先出(Last In First Out)的线性表,简称LIFO结构

栈的常见基本操作
InitStack(&S):初始化一个空栈S 。
StackEmpty(S):判断一个栈是否为空,若栈为空则返回true,否则返回false。
Push(&S, x):进栈(栈的插入操作),若栈S未满,则将x加入使之成为新栈顶。
Pop(&S, &x):出栈(栈的删除操作),若栈S非空,则弹出栈顶元素,并用x返回。
GetTop(S, &x):读栈顶元素,若栈S非空,则用x返回栈顶元素。
DestroyStack(&S):栈销毁,并释放S占用的存储空间(“&”表示引用调用)。

1.2 栈的实现(数组模拟)

栈的实现可以使用数组来模拟。我们可以定义一个固定大小的数组和一个指向栈顶的指针。栈顶指针初始化为 -1,表示栈为空。每次入栈操作时,将元素放入数组并将栈顶指针加一;出栈操作时,将栈顶指针减一。

#include 
using namespace std;

const int MAX_SIZE = 100; // 栈的最大容量
int stack[MAX_SIZE]; // 栈的数组
int top = -1; // 栈顶指针

// 入栈操作
void push(int x) {
    if (top >= MAX_SIZE - 1) {
        cout << "栈已满,无法入栈!" << endl;
        return;
    }
    stack[++top] = x;
}

// 出栈操作
void pop() {
    if (top == -1) {
        cout << "栈为空,无法出栈!" << endl;
        return;
    }
    top--;
}

// 获取栈顶元素
int peek() {
    if (top == -1) {
        cout << "栈为空,无栈顶元素!" << endl;
        return -1;
    }
    return stack[top];
}

// 判断栈是否为空
bool isEmpty() {
    return top == -1;
}

// 获取栈的大小
int size() {
    return top + 1;
}

int main() {
    push(1);
    push(2);
    push(3);
    
    cout << "当前栈顶元素:" << peek() << endl;
    cout << "栈的大小:" << size() << endl;
    
    pop();
    cout << "当前栈顶元素:" << peek() << endl;
    cout << "栈是否为空:" << (isEmpty() ? "是" : "否") << endl;
    
    return 0;
}

运行结果:

当前栈顶元素:3
栈的大小:3
当前栈顶元素:2
栈是否为空:否

1.3 栈的实现(STL 方法)

在 C++ 中,我们也可以使用标准模板库(STL)提供的 stack 容器来实现栈。使用 stack 容器非常方便,不需要手动处理数组和指针。

#include 
#include 
using namespace std;

int main() {
    stack<int> st;
    
    st.push(1);
    st.push(2);
    st.push(3);
    
    cout << "当前栈顶元素:" << st.top() << endl;
    cout << "栈的大小:" << st.size() << endl;
    
    st.pop();
    cout << "当前栈顶元素:" << st.top() << endl;
    cout << "栈是否为空:" << (st.empty() ? "是" : "否") << endl;
    
    return 0;
}

运行结果:

当前栈顶元素:3
栈的大小:3
当前栈顶元素:2
栈是否为空:否

使用 stack 容器,我们可以更方便地实现栈的入栈、出栈、获取栈顶元素等操作,而无需自己处理底层的数据结构。

1.4 出入栈顺序(火车调度案例)

火车调度是一个经典的栈的应用场景。给定 n 辆火车的进站顺序,请输出所有可能的出站顺序。

我们使用递归来生成所有可能的出站顺序。对于每一辆火车,它可以先进站再出站,也可以直接从进站过程中出站。我们不断尝试所有可能的出站顺序,直到所有火车都出站。

#include 
#include 
using namespace std;

void dfs(vector<int>& in, vector<int>& out, vector<int>& path) {
    if (in.empty() && out.empty()) {
        for (int num : path) {
            cout << num << " ";
        }
        cout << endl;
        return;
    }
    
    if (!in.empty()) {
        int num = in.back();
        in.pop_back();
        path.push_back(num);
        dfs(in, out, path);
        path.pop_back();
        in.push_back(num);
    }
    
    if (!out.empty()) {
        int num = out.back();
        out.pop_back();
        path.push_back(num);
        dfs(in, out, path);
        path.pop_back();
        out.push_back(num);
    }
}

int main() {
    vector<int> in = {1, 2, 3};
    vector<int> out;
    vector<int> path;
    dfs(in, out, path);
    
    return 0;
}

运行结果:

1 2 3 
1 3 2 
2 1 3 
2 3 1 
3 1 2 
3 2 1 

1.5 栈的应用

有效的括号

题目描述

给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。

示例 1:

输入:s = "()"
输出:true

示例 2:

输入:s = "()[]{}"
输出:true

示例 3:

输入:s = "(]"
输出:false

提示:

  • 1 <= s.length <= 104
  • s 仅由括号 '()[]{}' 组成

力扣官方题解https://leetcode.cn/problems/valid-parentheses/solutions/373578/you-xiao-de-gua-hao-by-leetcode-solution/

我们遍历给定的字符串 s。当我们遇到一个左括号时,我们会期望在后续的遍历中,有一个相同类型的右括号将其闭合。由于后遇到的左括号要先闭合,因此我们可以将这个左括号放入栈顶。
当我们遇到一个右括号时,我们需要将一个相同类型的左括号闭合。此时,我们可以取出栈顶的左括号并判断它们是否是相同类型的括号。如果不是相同的类型,或者栈中并没有左括号,那么字符串 s 无效,返回False。为了快速判断括号的类型,我们可以使用哈希表存储每一种括号。哈希表的键为右括号,值为相同类型的左括号。
在遍历结束后,如果栈中没有左括号,说明我们将字符串 s 中的所有左括号闭合,返回 True,否则返回 False。
注意到有效字符串的长度一定为偶数,因此如果字符串的长度为奇数,我们可以直接返回 False,省去后续的遍历判断过程。

栈在括号匹配中有着重要的应用。给定一个只包含 ()[]{} 的字符串,判断括号是否匹配。

使用栈可以轻松解决这个问题。遍历字符串中的每个字符,如果是左括号,将其入栈;如果是右括号,判断与栈顶元素是否匹配,若匹配则出

栈,否则返回 false。最后检查栈是否为空,为空则括号匹配。

#include 
#include 
#include 
using namespace std;

bool isValid(string s) {
    stack<char> st;
    unordered_map<char, char> mapping = {{')', '('}, {']', '['}, {'}', '{'}};
    
    for (char c : s) {
        if (c == '(' || c == '[' || c == '{') {
            st.push(c);
        } else if (c == ')' || c == ']' || c == '}') {
            if (st.empty() || st.top() != mapping[c]) {
                return false;
            }
            st.pop();
        }
    }
    
    return st.empty();
}

int main() {
    cout << isValid("()") << endl;         // 输出:1 (true)
    cout << isValid("()[]{}") << endl;     // 输出:1 (true)
    cout << isValid("(]") << endl;         // 输出:0 (false)
    
    return 0;
}

17-C++ 数据结构 - 栈_第2张图片

17-C++ 数据结构 - 栈_第3张图片

总结

栈是一种非常重要的数据结构,在计算机算法中有着广泛的应用。通过模拟入栈和出栈操作,我们可以解决很多实际问题,比如火车调度、括号匹配等。同时,C++ 中也提供了标准模板库(STL)中的 stack 容器,方便我们使用栈来解决问题。熟练掌握栈的使用将会对你的编程技能有很大的提升。

⭐️希望本篇文章对你有所帮助。

⭐️如果你有任何问题或疑惑,请随时向提问。

⭐️感谢阅读!

你可能感兴趣的:(c++,数据结构,开发语言)