首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 堆疊完整實作:陣列/鏈表/泛型/雙堆疊/中綴轉後綴/表達式求值——從資料結構到編譯器前端核心

     編輯:關於C語言

前言:堆疊是計算機科學的基石

從函數呼叫堆疊、瀏覽器前進後退、編輯器撤銷重做,到編譯器語法分析、表達式求值、DFS 圖遍歷,堆疊無處不在。本文實作多種堆疊變體,並深入演示中綴轉後綴(Shunting Yard)與後綴表達式求值——編譯器前端與計算器的核心算法。

一、多種堆疊實作對比

1. 陣列堆疊(固定容量、高效能)


public class ArrayStack {
    private T[] _items;
    private int _top; // 指向下一個可寫入位置,初始 0

    public ArrayStack(int capacity) {
        if (capacity <= 0) throw new ArgumentOutOfRangeException();
        _items = new T[capacity];
        _top = 0;
    }

    public int Count => _top;
    public bool IsEmpty => _top == 0;

    public void Push(T item) {
        if (_top == _items.Length) throw new InvalidOperationException("Stack overflow");
        _items[_top++] = item;
    }

    public T Pop() {
        if (IsEmpty) throw new InvalidOperationException("Stack underflow");
        T item = _items[--_top];
        _items[_top] = default; // 釋放引用助 GC
        return item;
    }

    public T Peek() => IsEmpty ? throw new InvalidOperationException() : _items[_top - 1];
}

2. 鏈表堆疊(動態擴容、無容量上限)


public class LinkedStack {
    private class Node {
        public T Value;
        public Node Next;
        public Node(T val, Node next) { Value = val; Next = next; }
    }
    private Node _head;

    public int Count { get; private set; }
    public bool IsEmpty => _head == null;

    public void Push(T item) {
        _head = new Node(item, _head);
        Count++;
    }

    public T Pop() {
        if (IsEmpty) throw new InvalidOperationException();
        T val = _head.Value;
        _head = _head.Next;
        Count--;
        return val;
    }

    public T Peek() => IsEmpty ? throw new InvalidOperationException() : _head.Value;
}

3. 雙堆疊共享陣列(經典資料結構習題)


public class DualArrayStack {
    private double[] _items;
    private int _top1; // 堆疊1從左向右增長
    private int _top2; // 堆疊2從右向左增長

    public DualArrayStack(int totalCapacity) {
        _items = new double[totalCapacity];
        _top1 = 0;
        _top2 = totalCapacity - 1;
    }

    public void Push1(double v) {
        if (_top1 > _top2) throw new InvalidOperationException("Stack overflow");
        _items[_top1++] = v;
    }
    public double Pop1() => _items[--_top1];

    public void Push2(double v) {
        if (_top1 > _top2) throw new InvalidOperationException("Stack overflow");
        _items[_top2--] = v;
    }
    public double Pop2() => _items[++_top2];
}

二、字元堆疊:中綴轉後綴(Shunting Yard Algorithm)

中綴:3 + 4 * 2 / (1 - 5) → 後綴:3 4 2 * 1 5 - / +

核心邏輯

  1. 掃描中綴運算式字元。
  2. 運算元直接輸出。
  3. 運算子依優先序與結合性決定:彈出堆疊頂較高/同優先序運算子輸出,再壓入當前運算子。
  4. 左括號入堆疊;右括號彈出至左括號為止(左括號不輸出)。
  5. 掃描結束,彈出剩餘運算子。

完整實作(支援多位數、小數、空白)


public static class InfixToPostfix {
    private static int Precedence(char op) => op switch {
        '+' or '-' => 1,
        '*' or '/' => 2,
        '^' => 3,
        _ => 0
    };

    public static string Convert(string infix) {
        var output = new List();
        var ops = new Stack();
        int i = 0, n = infix.Length;

        while (i < n) {
            char c = infix[i];
            if (char.IsWhiteSpace(c)) { i++; continue; }

            // 數字(含多位數、小數)
            if (char.IsDigit(c) || c == '.') {
                int start = i;
                while (i < n && (char.IsDigit(infix[i]) || infix[i] == '.')) i++;
                output.Add(infix.Substring(start, i - start));
                continue;
            }

            // 左括號
            if (c == '(') { ops.Push(c); i++; continue; }

            // 右括號:彈出至左括號
            if (c == ')') {
                while (ops.Count > 0 && ops.Peek() != '(')
                    output.Add(ops.Pop().ToString());
                if (ops.Count == 0) throw new ArgumentException("括號不匹配");
                ops.Pop(); // 丟棄左括號
                i++; continue;
            }

            // 運算子
            if ("+-*/^".Contains(c)) {
                while (ops.Count > 0 && ops.Peek() != '(' && 
                       Precedence(ops.Peek()) >= Precedence(c))
                    output.Add(ops.Pop().ToString());
                ops.Push(c);
                i++; continue;
            }

            throw new ArgumentException($"非法字元: {c}");
        }

        while (ops.Count > 0) {
            if (ops.Peek() == '(') throw new ArgumentException("括號不匹配");
            output.Add(ops.Pop().ToString());
        }

        return string.Join(" ", output);
    }
}

三、雙精度堆疊:後綴表達式求值


public static class PostfixEvaluator {
    public static double Evaluate(string postfix) {
        var stack = new Stack();
        var tokens = postfix.Split(' ', StringSplitOptions.RemoveEmptyEntries);

        foreach (string tok in tokens) {
            if (double.TryParse(tok, out double num)) {
                stack.Push(num);
            } else if (tok.Length == 1 && "+-*/^".Contains(tok)) {
                if (stack.Count < 2) throw new ArgumentException("運算元不足");
                double b = stack.Pop(); // 右運算元
                double a = stack.Pop(); // 左運算元
                double result = tok[0] switch {
                    '+' => a + b,
                    '-' => a - b,
                    '*' => a * b,
                    '/' => b == 0 ? throw new DivideByZeroException() : a / b,
                    '^' => Math.Pow(a, b),
                    _ => throw new ArgumentException($"未知運算子: {tok}")
                };
                stack.Push(result);
            } else {
                throw new ArgumentException($"無效 Token: {tok}");
            }
        }

        if (stack.Count != 1) throw new ArgumentException("運算式錯誤");
        return stack.Pop();
    }
}

四、整合測試:從中綴字串到數值結果


string infix = "3 + 4 * 2 / ( 1 - 5 ) ^ 2 ^ 3";
string postfix = InfixToPostfix.Convert(infix);
Console.WriteLine($"中綴: {infix}");
Console.WriteLine($"後綴: {postfix}");
// 輸出: 3 4 2 * 1 5 - 2 3 ^ ^ / +

double result = PostfixEvaluator.Evaluate(postfix);
Console.WriteLine($"結果: {result}"); // 約 3.000122

五、進階擴展與避坑指南

主題關鍵點
一元運算子(負號)需區分一元 - 與二元 -:掃描時若 - 前為運算子/左括號/開頭,視為一元,可轉為 0 - operand 或引入 ~ 符號
函數支援 (sin, max 等)Shunting Yard 擴展:函數名入堆疊,遇逗號彈出至左括號,遇右括號彈出函數名輸出
右結合冪運算 ^優先序相同時,右結合不彈出(> 而非 >=),上述代碼已處理
錯誤處理括號不匹配、運算元不足、除零、非法字元、最終堆疊非單一值
效能優化字串分割分配記憶體,高頻計算可用 Span 手寫 Tokenizer 避免分配
實務替代生產環境建議 NCalc、Flee、Jace.NET 等成熟表達式引擎

六、堆疊在編譯器與系統中的其他經典應用

  • 遞迴下降剖析器:運算子優先序剖析隱式使用呼叫堆疊,顯式堆疊可轉為非遞迴。
  • AST 建構:運算子堆疊 + 運算元堆疊 → 建構抽象語法樹節點。
  • 括號匹配/語法高亮:編輯器即時檢查 { [ ( ) ] } 配對。
  • DFS / 拓撲排序 / Tarjan SCC:圖算法核心資料結構。
  • 瀏覽器歷史/編輯器 Undo-Redo:雙堆疊 (UndoStack, RedoStack) 實作。

總結

從 ArrayStack、LinkedStack、DualArrayStack 三種底層實作,到 InfixToPostfix.Convert 與 PostfixEvaluator.Evaluate 兩大核心算法,本文完整串聯了堆疊資料結構 → 表達式轉換 → 表達式求值的完整鏈路。掌握 Shunting Yard 算法細節(優先序、結合性、括號、一元運算子、函數),即可自行實作計算器、DSL 解析器、配置語言直譯器等編譯器前端核心組件。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved