Stack<T>​ 学习笔记

一、基础概念与用法

1. 什么是 Stack?

Stack<T> 是一种遵循 “后进先出”(LIFO: Last In, First Out) 原则的泛型集合。

  • 类比理解
    就像往一个筒里放书——只能从顶部放入,也只能从顶部取出。最后放进去的书,最先被拿出来。

  • 命名空间

    using System.Collections.Generic;
    

2. 创建 Stack

// 空栈
Stack<string> stack = new Stack<string>();

// 初始化时添加元素
Stack<int> numbers = new Stack<int>(new int[] { 10, 20, 30 });
// 栈中从底到顶:10 → 20 → 30(30 在最上面)

3. 常用方法与属性

方法 / 属性 作用说明 时间复杂度
Push(item) 将元素压入栈顶 O(1)
Pop() 移除并返回栈顶元素(会修改栈) O(1)
Peek() 查看栈顶元素,但不移除 O(1)
Count 获取栈中元素数量 O(1)
Clear() 清空整个栈 O(n)
Contains(item) 判断是否包含某元素 O(n)
ToArray() 将栈转为数组(顺序:从顶到底) O(n)

⚠️ 重要提醒
调用 Pop()Peek() 时,若栈为空,会抛出 InvalidOperationException
安全做法:先检查 Count > 0


4. 基础示例

Stack<string> actions = new Stack<string>();

actions.Push("保存文件");
actions.Push("编辑文本");
actions.Push("打开文档");

Console.WriteLine($"待处理操作数:{actions.Count}");        // 输出:3
Console.WriteLine($"当前操作:{actions.Peek()}");           // 输出:打开文档

string current = actions.Pop();
Console.WriteLine($"执行:{current}");                      // 输出:执行:打开文档

// 遍历(从顶到底)
foreach (string action in actions)
{
    Console.WriteLine(action);
}
// 输出:
// 编辑文本
// 保存文件

💡 注意:foreach 遍历顺序是 从栈顶到栈底,不是插入顺序。


二、进阶知识点

1. 与其他集合的对比

集合类型 访问方式 插入/删除位置 顺序性 典型用途
List<T> 索引随机访问 任意位置 通用列表、频繁读写
Queue<T> 先进先出 (FIFO) 队尾入,队头出 任务队列、消息缓冲
Stack<T> 后进先出 (LIFO) 仅栈顶 撤销、回溯、匹配
HashSet<T> 无索引 任意(自动去重) 快速查找、去重

关键区别
Stack<T> 只允许在栈顶操作,设计目标明确,不适合随机访问。


2. 性能特点

  • 底层实现:基于动态数组(T[]),自动扩容。

  • 高效操作

    • Push / Pop / PeekO(1) ,非常快。
  • 低效操作

    • Contains / Clear / ToArrayO(n) ,需遍历全部元素。
  • 内存开销:略高于数组,但远低于链表类结构。

📌 建议
若需频繁“压入/弹出”,用 Stack<T>
若需频繁“查找/中间插入”,改用 List<T>


3. 线程安全问题

  • Stack<T> 不是线程安全的

    • 多线程同时操作可能导致异常或数据错乱。

解决方案:

  1. 手动加锁(适用于简单场景)

    private static readonly object _lock = new();
    lock (_lock)
    {
        myStack.Push(item);
    }
    
  2. 使用线程安全版本:****ConcurrentStack<T>

    using System.Collections.Concurrent;
    
    ConcurrentStack<int> safeStack = new();
    safeStack.Push(42);
    bool success = safeStack.TryPop(out int value); // 安全弹出
    

✅ 初学者建议:
单线程 → Stack<T>
多线程 → 优先选择 ConcurrentStack<T>


三、实际工作中的使用场景

场景 1:撤销(Undo)功能

  • 原理:每次操作压入栈,撤销时弹出并反向执行。

  • 示例

    Stack<Action> undoActions = new();
    
    // 用户输入
    undoActions.Push(() => textBox.Text = previousText);
    
    // 撤销
    if (undoActions.Count > 0)
    {
        undoActions.Pop().Invoke(); // 执行撤销动作
    }
    

场景 2:括号匹配检查

  • 用途:校验 {[()]}、HTML 标签是否正确闭合。

  • 逻辑

    • 遇到左括号 → Push
    • 遇到右括号 → Pop 并验证是否匹配
    • 最终栈应为空
bool IsValid(string s)
{
    Stack<char> stack = new();
    var pairs = new Dictionary<char, char> { { ')', '(' }, { ']', '[' }, { '}', '{' } };

    foreach (char c in s)
    {
        if ("([{".Contains(c))
            stack.Push(c);
        else if (stack.Count == 0 || stack.Pop() != pairs[c])
            return false;
    }
    return stack.Count == 0;
}

场景 3:浏览器“后退”功能

  • 实现

    • 访问新页面 → Push 到历史栈
    • 点击“后退” → Pop 当前页,显示新栈顶

场景 4:深度优先搜索(DFS)

  • 在树或图遍历中,非递归 DFS 通常用 Stack 实现

  • 示例(遍历文件夹):

    Stack<string> folders = new();
    folders.Push(rootPath);
    
    while (folders.Count > 0)
    {
        string current = folders.Pop();
        foreach (string sub in Directory.GetDirectories(current))
            folders.Push(sub);
        // 处理 current
    }
    

场景 5:表达式求值与编译器

  • 中缀表达式 → 后缀表达式(逆波兰)转换
  • 运算符优先级处理
  • 函数调用栈模拟(底层原理)

总结速查表

项目 说明
核心原则 后进先出(LIFO)
关键操作 Push(入)、Pop(出)、Peek(看)
性能优势 O(1) 的入栈/出栈
常见陷阱 空栈调用 Pop/Peek 会崩溃
线程安全 否 → 用 ConcurrentStack<T>
典型场景 撤销、匹配、回退、DFS、表达式解析

🌟 一句话口诀
“栈顶操作,后进先出;高效简洁,慎防空栈。”

posted @ 2025-10-31 11:24  恨水长秋  阅读(17)  评论(0)    收藏  举报