一、数据结构入门:从数组说起

数据结构就像我们生活中的储物柜,数组是最简单的"格子间"。它最大的特点就是所有元素在内存中连续排列,就像超市货架上挨着的饮料瓶。

在C#中,数组是这样工作的:

// 技术栈:C#/.NET 6
// 创建包含5个整数的数组
int[] scores = new int[5] { 90, 85, 78, 92, 88 };

// 访问第三个元素(注意索引从0开始)
Console.WriteLine($"第三位成绩:{scores[2]}"); // 输出:78

// 修改最后一个元素
scores[4] = 95;

// 遍历数组的现代写法
foreach (var score in scores)
{
    Console.Write($"{score} "); // 输出:90 85 78 92 95
}

应用场景

  • 固定长度的数据集合(如学生成绩表)
  • 需要快速随机访问的场景(索引访问时间复杂度O(1))

注意事项

  • 数组长度不可变,需要提前确定大小
  • 插入/删除元素时需要移动后续所有元素

二、灵活变通的链表

如果说数组是固定座位的教室,链表就像可以随时加座的咖啡厅。每个元素(节点)都记住下一个伙伴的位置。

C#实现单向链表:

// 技术栈:C#/.NET 6
public class ListNode
{
    public int Value { get; set; }
    public ListNode Next { get; set; }
    
    public ListNode(int value) => Value = value;
}

// 创建链表:1 → 3 → 5
var head = new ListNode(1);
head.Next = new ListNode(3);
head.Next.Next = new ListNode(5);

// 遍历链表
var current = head;
while (current != null)
{
    Console.Write($"{current.Value}→");
    current = current.Next; // 输出:1→3→5→
}

技术对比

  • 插入效率:链表O(1) vs 数组O(n)
  • 访问效率:链表O(n) vs 数组O(1)

实际应用

  • 浏览器历史记录(频繁增删)
  • 内存池管理系统

三、后进先出的栈

栈就像叠盘子,最后放上去的总是最先被拿走。C#中可以用Stack<T>类:

// 技术栈:C#/.NET 6
var browserHistory = new Stack<string>();

// 压栈操作
browserHistory.Push("首页");
browserHistory.Push("产品页");
browserHistory.Push("购物车");

// 查看栈顶
Console.WriteLine($"当前页面:{browserHistory.Peek()}"); // 输出:购物车

// 退栈操作
while (browserHistory.Count > 0)
{
    Console.WriteLine($"返回:{browserHistory.Pop()}");
    // 输出顺序:购物车 → 产品页 → 首页
}

典型场景

  • 函数调用栈
  • 撤销操作(Undo)实现
  • 括号匹配检查

四、先进先出的队列

队列就像食堂排队,先来的人先打饭。.NET提供了Queue<T>类:

// 技术栈:C#/.NET 6
var printerQueue = new Queue<string>();

// 入队
printerQueue.Enqueue("财务报告.docx");
printerQueue.Enqueue("季度报表.xlsx");
printerQueue.Enqueue("会议纪要.pdf");

// 处理队列
while (printerQueue.Count > 0)
{
    var file = printerQueue.Dequeue();
    Console.WriteLine($"正在打印:{file}");
    // 输出顺序与入队顺序一致
}

扩展应用

  • 消息队列系统(如RabbitMQ的基础)
  • 多线程任务调度
  • 广度优先搜索算法

五、技术选型指南

当我们需要选择数据结构时,可以这样考虑:

  1. 访问模式

    • 频繁随机访问 → 数组
    • 只从两端操作 → 栈/队列
  2. 内存考虑

    • 内存紧张 → 数组(无额外指针开销)
    • 需要动态扩展 → 链表
  3. 线程安全
    .NET提供了ConcurrentStackConcurrentQueue等线程安全版本

六、实战:用数组实现栈

为了加深理解,我们手动实现栈:

// 技术栈:C#/.NET 6
public class ArrayStack<T>
{
    private T[] _items;
    private int _top = -1;
    
    public ArrayStack(int capacity) => _items = new T[capacity];
    
    public void Push(T item)
    {
        if (_top == _items.Length - 1)
            throw new StackOverflowException();
            
        _items[++_top] = item;
    }
    
    public T Pop()
    {
        if (_top == -1)
            throw new InvalidOperationException("栈为空");
            
        return _items[_top--];
    }
}

// 使用示例
var stack = new ArrayStack<int>(3);
stack.Push(10);
stack.Push(20);
Console.WriteLine(stack.Pop()); // 输出20

七、性能陷阱与优化

  1. 数组扩容
    List在.NET中的扩容策略是每次翻倍,频繁扩容会影响性能

  2. 缓存友好性
    数组的连续内存特性对CPU缓存更友好

  3. GC压力
    链表会产生更多内存碎片,增加GC负担

八、现代.NET中的增强功能

.NET Core以后新增的特性:

// 技术栈:C#/.NET 6
// 范围操作(数组切片)
int[] numbers = { 0, 1, 2, 3, 4 };
var slice = numbers[1..3]; // 包含1, 2

// 只读内存区域
var memory = new ReadOnlyMemory<int>(numbers);

// 高性能Span
Span<int> span = numbers.AsSpan();
span[1] = 99; // 直接修改原数组

九、总结与展望

掌握基础数据结构就像学会了烹饪的基本刀工。虽然现代开发中我们更多使用现成的集合类,但理解它们的原理才能:

  • 在性能敏感场景做出正确选择
  • 更好地理解高级数据结构(如哈希表、树)
  • 应对技术面试中的算法问题

下次当你使用List时,不妨想想它背后是数组实现的,而当你使用LinkedList时,就知道它更适合频繁插入的场景。数据结构的选择,往往决定了程序的效率基因。