2024年6月3日 星期一

12.VB.NET 筆記 基礎篇 - 堆疊 (Stack)

VB.NET 堆疊(Stack)筆記(基礎篇)

VB.NET 堆疊(Stack) 筆記(基礎篇)

Stack(Of T) 是用來保存資料的一種集合,特色是「最後放入的資料,會最先被取出」。這種順序稱為 LIFO,也就是 Last In, First Out。

堆疊適合處理需要「回到最近一步」、「先處理最上層」、「暫存一連串待還原動作」的流程。理解 Stack 時,重點不是背方法名稱,而是看懂資料從哪裡進、從哪裡出,以及為什麼只能優先操作頂端資料。

先理解 Stack 的順序

Stack 的核心:後放上去的先拿下來

Stack 可以想成一疊由上往下堆放的資料。新增資料時放在最上面;取資料時,也從最上面開始取。因此最後加入的資料,會比前面加入的資料更早被處理。

  • Push:把資料放到堆疊頂端。
  • Pop:取出頂端資料,並從堆疊移除。
  • Peek:查看頂端資料,但不移除。
  • Count:查看目前有幾筆資料。

適合使用 Stack 的判斷方式:

  1. 只需要處理最新加入的資料:例如最新動作、最上層項目、最近一筆紀錄。
  2. 需要回復最近一步:例如撤銷、退回、回到上一個狀態。
  3. 需要成對檢查:例如開啟標記與關閉標記是否正確對應。
操作 做什麼 是否改變堆疊
Push 把資料放到頂端。 會增加一筆資料。
Pop 取出頂端資料。 會移除一筆資料。
Peek 查看頂端資料。 不會移除資料。
Count 取得資料筆數。 不會移除資料。

Stack(Of T) 的基本操作

場景一:客服便條堆疊

這個範例用客服便條來示範 PushPeekPop。新便條會放到最上方,查看時看到最上方便條,處理時也會先取出最上方便條。

需要的主控項
  • TextBoxSlip:輸入便條內容。
  • ButtonAddSlip:放入便條。
  • ButtonViewTopSlip:查看最上方便條。
  • ButtonProcessSlip:處理最上方便條。
  • LabelSlipStatus:顯示目前狀態。
  • LabelSlipCount:顯示便條數量。
範例程式碼
VB.NET / Windows Forms
Imports System.Collections.Generic

Public Class Form1
    Private serviceSlips As New Stack(Of String)

    Private Sub ButtonAddSlip_Click(sender As Object, e As EventArgs) Handles ButtonAddSlip.Click
        Dim slipText As String = TextBoxSlip.Text.Trim()

        If slipText = String.Empty Then
            LabelSlipStatus.Text = "請先輸入便條內容"
            Return
        End If

        serviceSlips.Push(slipText)
        TextBoxSlip.Clear()
        RefreshSlipStatus("已放入便條:" & slipText)
    End Sub

    Private Sub ButtonViewTopSlip_Click(sender As Object, e As EventArgs) Handles ButtonViewTopSlip.Click
        If serviceSlips.Count = 0 Then
            RefreshSlipStatus("目前沒有便條")
            Return
        End If

        RefreshSlipStatus("最上方便條:" & serviceSlips.Peek())
    End Sub

    Private Sub ButtonProcessSlip_Click(sender As Object, e As EventArgs) Handles ButtonProcessSlip.Click
        If serviceSlips.Count = 0 Then
            RefreshSlipStatus("沒有可處理的便條")
            Return
        End If

        Dim doneSlip As String = serviceSlips.Pop()
        RefreshSlipStatus("已處理便條:" & doneSlip)
    End Sub

    Private Sub RefreshSlipStatus(ByVal message As String)
        LabelSlipStatus.Text = message
        LabelSlipCount.Text = "便條數量:" & serviceSlips.Count.ToString()
    End Sub
End Class
操作結果範例
依序放入:查詢發票、修改電話、補寄通知 查看最上方便條:補寄通知 處理最上方便條:補寄通知 便條數量:2
邏輯解析
  • Push 會把新便條放到最上方。
  • Peek 只查看最上方便條,數量不會減少。
  • Pop 會取出並移除最上方便條。
  • 每次 PopPeek 前都先檢查 Count,避免空堆疊例外。

應用一:撤銷最近動作

場景二:海報編輯動作撤銷

撤銷功能很適合使用 Stack。每次編輯都把動作記錄放進堆疊;按下撤銷時,最後做的動作會最先被取出。

需要的主控項
  • ButtonAddTitle:加入標題動作。
  • ButtonAddPhoto:加入照片動作。
  • ButtonChangeColor:加入換色動作。
  • ButtonUndo:撤銷最近動作。
  • TextBoxLog:顯示動作紀錄,建議設定 Multiline=True
範例程式碼
VB.NET / Windows Forms
Imports System.Collections.Generic

Public Class PosterAction
    Public Property ActionName As String
    Public Property DetailText As String

    Public Sub New(ByVal actionName As String, ByVal detailText As String)
        Me.ActionName = actionName
        Me.DetailText = detailText
    End Sub

    Public Function BuildText() As String
        Return ActionName & "|" & DetailText
    End Function
End Class

Public Class Form1
    Private undoStack As New Stack(Of PosterAction)

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        TextBoxLog.Multiline = True
        TextBoxLog.ScrollBars = ScrollBars.Vertical
    End Sub

    Private Sub ButtonAddTitle_Click(sender As Object, e As EventArgs) Handles ButtonAddTitle.Click
        AddPosterAction("加入標題", "春季活動海報")
    End Sub

    Private Sub ButtonAddPhoto_Click(sender As Object, e As EventArgs) Handles ButtonAddPhoto.Click
        AddPosterAction("加入照片", "主視覺圖片")
    End Sub

    Private Sub ButtonChangeColor_Click(sender As Object, e As EventArgs) Handles ButtonChangeColor.Click
        AddPosterAction("更換色系", "橘色主題")
    End Sub

    Private Sub ButtonUndo_Click(sender As Object, e As EventArgs) Handles ButtonUndo.Click
        If undoStack.Count = 0 Then
            TextBoxLog.AppendText("沒有可撤銷的動作" & Environment.NewLine)
            Return
        End If

        Dim lastAction As PosterAction = undoStack.Pop()
        TextBoxLog.AppendText("撤銷:" & lastAction.BuildText() & Environment.NewLine)
    End Sub

    Private Sub AddPosterAction(ByVal actionName As String, ByVal detailText As String)
        Dim action As New PosterAction(actionName, detailText)
        undoStack.Push(action)
        TextBoxLog.AppendText("完成:" & action.BuildText() & Environment.NewLine)
    End Sub
End Class
操作結果範例(TextBoxLog)
完成:加入標題|春季活動海報 完成:加入照片|主視覺圖片 完成:更換色系|橘色主題 撤銷:更換色系|橘色主題
邏輯解析
  • 每次編輯海報時,都把動作物件放入 undoStack
  • 最後完成的「更換色系」位於頂端,因此最先被撤銷。
  • 堆疊不需要搜尋中間資料,因為撤銷永遠處理最近一次動作。

應用二:觀察堆疊列舉順序

場景三:收納箱從上往下列出

列舉 Stack 時,會從頂端開始往下列出。這點和一般 List 的直覺不同,因此很適合用收納箱示範:最後放上去的物品會先被看到。

需要的主控項
  • ButtonShowBox:顯示收納箱內容。
  • ListBoxBoxItems:列出物品順序。
範例程式碼
VB.NET / Windows Forms
Imports System.Collections.Generic

Public Class Form1
    Private boxItems As New Stack(Of String)

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        boxItems.Push("桌墊")
        boxItems.Push("筆記本")
        boxItems.Push("充電線")
        boxItems.Push("識別證套")
    End Sub

    Private Sub ButtonShowBox_Click(sender As Object, e As EventArgs) Handles ButtonShowBox.Click
        ListBoxBoxItems.Items.Clear()

        For Each itemName As String In boxItems
            ListBoxBoxItems.Items.Add(itemName)
        Next
    End Sub
End Class
畫面輸出結果(ListBoxBoxItems)
識別證套 充電線 筆記本 桌墊
邏輯解析
  • 加入順序是桌墊、筆記本、充電線、識別證套。
  • 列舉時從頂端開始,因此先看到最後放入的識別證套。
  • 若需要按照原始加入順序顯示,Stack 不是最直覺的集合。

應用三:檢查成對標記

場景四:檢查菜單模板標記是否閉合

成對符號檢查是 Stack 的典型應用。這裡不用抽象括號題,而是改成菜單模板:模板中可能使用 {}[]() 標記欄位,檢查時需要確認每個開啟標記都有正確閉合。

需要的主控項
  • TextBoxTemplate:輸入菜單模板文字。
  • ButtonCheckTemplate:檢查標記。
  • LabelCheckResult:顯示檢查結果。
範例程式碼
VB.NET / Windows Forms
Imports System.Collections.Generic

Public Class Form1
    Private Sub ButtonCheckTemplate_Click(sender As Object, e As EventArgs) Handles ButtonCheckTemplate.Click
        Dim templateText As String = TextBoxTemplate.Text

        If IsMarkerPairValid(templateText) Then
            LabelCheckResult.Text = "模板標記正確"
        Else
            LabelCheckResult.Text = "模板標記不完整或順序錯誤"
        End If
    End Sub

    Private Function IsMarkerPairValid(ByVal text As String) As Boolean
        Dim markerStack As New Stack(Of Char)

        For Each ch As Char In text
            If ch = "("c OrElse ch = "["c OrElse ch = "{"c Then
                markerStack.Push(ch)
            ElseIf ch = ")"c OrElse ch = "]"c OrElse ch = "}"c Then
                If markerStack.Count = 0 Then
                    Return False
                End If

                Dim leftMarker As Char = markerStack.Pop()

                If Not IsPair(leftMarker, ch) Then
                    Return False
                End If
            End If
        Next

        Return markerStack.Count = 0
    End Function

    Private Function IsPair(ByVal leftMarker As Char, ByVal rightMarker As Char) As Boolean
        Return (leftMarker = "("c AndAlso rightMarker = ")"c) OrElse
               (leftMarker = "["c AndAlso rightMarker = "]"c) OrElse
               (leftMarker = "{"c AndAlso rightMarker = "}"c)
    End Function
End Class
畫面輸出結果(TextBoxTemplate = 今日套餐{主餐[飲品(冰量)]})
模板標記正確
邏輯解析
  • 遇到左標記時使用 Push 放入堆疊。
  • 遇到右標記時使用 Pop 取出最近的左標記進行配對。
  • 若右標記出現時堆疊是空的,代表沒有對應的左標記。
  • 檢查結束後堆疊必須為空,才代表所有左標記都已閉合。

Stack 與其他集合的差異

集合 適合情境 取資料順序
Stack(Of T) 撤銷、回退、最近資料優先處理。 後進先出。
Queue(Of T) 排隊、叫號、先到先處理。 先進先出。
List(Of T) 需要索引、排序、搜尋、列出全部資料。 依索引位置操作。

常見誤區

  • 把 Stack 當成一般清單:若常常需要讀中間元素,應改用 List(Of T)
  • 忘記空堆疊檢查:對空堆疊呼叫 PopPeek 會發生例外。
  • 需求其實是排隊:若要先進先出,應使用 Queue(Of T)
  • 只記方法不看順序:Stack 的重點是 LIFO,不是單純會新增與移除資料。

效能與使用限制

操作 時間複雜度 說明
Push O(1) 把元素放到頂端。
Pop O(1) 取出並移除頂端元素。
Peek O(1) 查看頂端元素。
Count O(1) 取得元素數量。

使用限制:Stack 很適合操作頂端資料,但不適合頻繁搜尋、排序或直接修改中間資料。若需求不是「最近加入的資料優先處理」,通常要先考慮其他集合。

重點整理

  1. Stack(Of T) 是後進先出的集合。
  2. Push 會把資料放到頂端。
  3. Pop 會取出並移除頂端資料。
  4. Peek 只查看頂端資料,不會移除。
  5. 呼叫 PopPeek 前應先檢查 Count
  6. Stack 適合撤銷、回退、最近資料優先處理與成對標記檢查。
  7. 若需求是先進先出,應使用 Queue(Of T)
  8. 若需求是索引、排序或搜尋,通常更適合 List(Of T)