網站首頁 編程語言 正文
0. 學習目標
棧和隊列是在程序設計中常見的數據類型,從數據結構的角度來講,棧和隊列也是線性表,是操作受限的線性表,它們的基本操作是線性表操作的子集,但從數據類型的角度來講,它們與線性表又有著巨大的不同。本節將首先介紹棧的定義和其不同實現,并且給出棧的一些實際應用。
通過本節學習,應掌握以下內容:
- 棧的基本概念及不同實現方法
- 棧基本操作的實現及時間復雜度
- 利用棧的基本操作實現復雜算法
1. 棧的基本概念
1.1 棧的基本概念
棧 (Stack) 是限定僅在序列一端執行插入和刪除操作的線性表,對于棧而言,可進行操作的一端稱為棧頂 (top),而另一端稱為棧底 (bottom)。如果棧中不含任何元素則稱其為空棧。
棧提供了一種基于在集合中的時間來排序的方式,最近添加的元素靠近頂端,舊元素則靠近底端。最新添加的元素被最先移除,這種排序原則也稱為后進先出 (last in first out, LIFO) 或先進后出 (fast in last out, FILO)。
棧在現實中的例子隨處可見,如下圖所示,球桶中的球構成了一個棧,每次只能從頂部取出一個,放回時也只能置于頂部。假設棧為S = ( a0 , a1 , … , en)為棧頂元素,棧中元素按的順序入棧 (push),而棧頂元素是第一個退棧 (pop) 的元素。
通過觀察元素的添加和移除順序,就可以快速理解棧所蘊含的思想。下圖展示了棧的入棧和出棧過程,棧中元素的插入順序和移除順序恰好是相反的。
1.2 棧抽象數據類型
除了主要的操作(入棧和出棧)外,棧還具有初始化、判空和取棧頂元素等輔助操作。具體而言,棧的抽象數據類型定義如下:
基本操作:
1. __itit__(): 初始化棧
創建一個空棧
2. size(): 求取并返回棧中所含元素的個數 n
若棧為空,則返回整數0
3. isempty(): 判斷是否為空棧
判斷棧中是否存儲元素
4. push(data): 入棧
將元素 data 插入棧頂
5. pop(): 出棧
刪除并返回棧頂元素
4. peek(): 取棧頂元素
返回棧頂元素值,但并不刪除元素
1.3 棧的應用場景
棧具有廣泛的應用場景,例如:
- 符號的匹配,具體描述參考第3.3小節;
- 函數調用,每個未結束調用的函數都會在函數棧中擁有一塊數據區,保存了函數的重要信息,包括函數的局部變量、參數等;
- 后綴表達式求值,計算后綴表達式只需一個用于存放數值的棧,遍歷表達式遇到數值則入棧,遇到運算符則出棧兩個數值進行計算,并將計算結果入棧,最后棧中保留的唯一值即為表達式結果;
- 網頁瀏覽中的返回按鈕,當我們在網頁間進行跳轉時,這些網址都被存放在一個棧中;
- 編輯器中的撤銷序列,與網頁瀏覽中的返回按鈕類似,棧保存每步的編輯操作。
除了以上應用外,我們在之后的學習中還將看到棧用作許多算法的輔助數據結構。
2. 棧的實現
和線性表一樣,棧同樣有兩種存儲表示方式。
2.1 順序棧的實現
順序棧是棧的順序存儲結構,其利用一組地址連續的存儲單元從棧底到棧頂依次存放。同時使用指針top來指示棧頂元素在順序棧中的索引,同樣順序棧可以是固定長度和動態長度,當棧滿時,定長順序棧會拋出棧滿異常,動態順序棧則會動態申請空閑空間。
2.1.1 棧的初始化
順序棧的初始化需要三部分信息:stack 列表用于存儲數據元素,max_size 用于存儲 stack 列表的最大長度,以及 top 用于記錄棧頂元素的索引:
class Stack: def __init__(self, max_size=10): self.max_size = max_size self.stack = self.max_size * [None] self.top = -1
2.1.2 求棧長
由于 top 表示棧頂元素的索引,我們可以據此方便的計算順序棧中的數據元素數量,即棧長:
def size(self): return self.top + 1
2.1.3 判棧空
根據棧的長度可以很容易的判斷棧是否為空棧:
def isempty(self): if self.size() == 0: return True else: return False
2.1.4 判棧滿
由于需要提前申請棧空間,因此我們需要能夠判斷棧是否還有空閑空間:
def isfully(self): if self.size() == self.max_size: return True else: return False
2.1.5 入棧
入棧時,需要首先判斷棧中是否還有空閑空間,然后根據棧為定長順序棧或動態順序棧,入棧操作稍有不同:
[定長順序棧的入棧操作] 如果棧滿,則引發異常:
def push(self, data): if self.isfully(): raise IndexError('Stack Overflow!') else: self.top += 1 self.stack[self.top_1] = data
[動態順序棧的入棧操作] 如果棧滿,則首先申請新空間:
def resize(self): new_size = 2 * self.max_size new_stack = [None] * new_size for i in range(self.num_items): new_stack[i] = self.items[i] self.stack = new_stack self.max_size = new_size def push(self, data): if self.isfully(): self.resize() else: self.top += 1 self.stack[self.top_1] = data
入棧的時間復雜度為O(1)。這里需要注意的是,雖然當動態順序棧滿時,原棧中的元素需要首先復制到新棧中,然后添加新元素,但根據《順序表及其操作實現》中順序表追加操作的介紹,由于n次入棧操作的總時間T(n) 與O(n) 成正比,因此入棧的攤銷時間復雜度仍可以認為是O(1)。
2.1.6 出棧
若棧不空,則刪除并返回棧頂元素:
def pop(self): if self.isempty(): raise IndexError('Stack Underflow!') else: result = self.stack[self.top] self.top -= 1 return result
2.1.7 求棧頂元素
若棧不空,則只需返回棧頂元素:
def peek(self): if self.isempty(): raise IndexError('Stack Underflow!') else: return self.stack[self.top]
2.2 鏈棧的實現
棧的另一種存儲表示方式是使用鏈式存儲結構,因此也常稱為鏈棧,其中 push 操作是通過在鏈表頭部插入元素來實現的,pop 操作是通過從頭部刪除節點來實現的。
2.2.1 棧結點
棧的結點實現與鏈表并無差別:
class Node: def __init__(self, data): self.data = data self.next = None def __str__(self): return str(self.data)
2.2.2 棧的初始化
棧的初始化函數中,使棧頂指針指向 None,并初始化棧長:
class Stack: def __init__(self): self.top = None # 棧中元素數 self.length = 0
2.2.3 求棧長
返回 length 的值用于求取棧的長度,如果沒有 length 屬性,則需要遍歷整個鏈表才能得到棧長:
def size(self): return self.length
2.2.4 判棧空
根據棧的長度可以很容易的判斷棧是否為空棧:
def isempty(self): if self.length == 0: return True else: return False
2.2.5 入棧
入棧時,在棧頂插入新元素即可:
def push(self, data): p = Node(data) p.next = self.top self.top = p self.length += 1
由于插入元素是在鏈表頭部進行的,因此入棧的時間復雜度為O(1),在這種情況下鏈尾作為棧底 。
2.2.6 出棧
若棧不空,則刪除并返回棧頂元素:
def pop(self): if self.isempty(): raise IndexError("Stack Underflow!") ele = self.top.data self.top = self.top.next self.length -= 1 return ele
由于刪除元素僅需修改頭指針指向其 next 域,因此出棧的時間復雜度同樣為O(1)。
2.2.7 求棧頂元素
若棧不空,返回棧頂元素即可,但棧頂元素并不會被刪除:
def peek(self): if self.isempty(): raise IndexError("Stack Underflow!") return self.top.data
2.3 棧的不同實現對比
本節我們將對比棧的不同實現之間的異同:
順序棧
- 操作的時間復雜度均為O(1),列表的尾部作為棧頂
- 棧滿時需要進行動態的擴展,復制原棧元素到新棧中
鏈棧
- 操作的時間復雜度均為O(1),鏈表的頭部作為棧頂
- 優雅的擴展,無需考慮棧滿,需要額外的空間存儲指針
3. 棧應用
接下來,我們首先測試上述實現的鏈表,以驗證操作的有效性,然后利用實現的基本操作來解決實際算法問題。
3.1 順序棧的應用
首先初始化一個順序棧 stack,然后測試相關操作:
# 初始化一個最大長度為4的棧 s = Stack(4) print('棧空?', s.isempty()) for i in range(4): print('入棧元素:', i) s.push(i) print('棧滿?', s.isfully()) print('棧頂元素:', s.peek()) print('棧長度為:', s.size()) while not s.isempty(): print('出棧元素:', s.pop())
測試程序輸出結果如下:
棧空? True
入棧元素: 0
入棧元素: 1
入棧元素: 2
入棧元素: 3
棧滿? True
棧頂元素: 3
棧長度為: 4
出棧元素: 3
出棧元素: 2
出棧元素: 1
出棧元素: 0
3.2 鏈棧的應用
首先初始化一個鏈棧 stack,然后測試相關操作:
# 初始化新棧 s = Stack() print('棧空?', s.isempty()) for i in range(4): print('入棧元素:', i) s.push(i) print('棧頂元素:', s.peek()) print('棧長度為:', s.size()) while not s.isempty(): print('出棧元素:', s.pop())
測試程序輸出結果如下:
棧空? True
入棧元素: 0
入棧元素: 1
入棧元素: 2
入棧元素: 3
棧頂元素: 3
棧長度為: 4
出棧元素: 3
出棧元素: 2
出棧元素: 1
出棧元素: 0
3.3 利用棧基本操作實現復雜算法
匹配符號是指正確地匹配左右對應的符號(符號允許進行嵌套),不僅每一個左符號都有一個右符號與之對應,而且兩個符號的類型也是一致的,下標展示了一些符號串的匹配情況:
符號串 | 是否匹配 |
---|---|
[]()() | 匹配 |
[(())() | 不匹配 |
{([]())} | 匹配 |
(())[]} | 不匹配 |
為了檢查符號串的匹配情況,需要遍歷符號串,如果字符是 (、[ 或 { 之類的開始分隔符,則將其寫入棧中;當遇到諸如 )、] 或 } 等結束分隔符時,則棧頂元素出棧,并將其與當前遍歷元素進行比較,如果它們匹配,則繼續解析符號串,否則表示不匹配。當遍歷完成后,如果棧不為空,則同樣表示不匹配:
def isvalid_expression(expression): stack = Stack() symbols = {')':'(', ']':'[', '}':'{'} for s in expression: if s in symbols: if stack: top_element = stack.pop() else: top_element = '#' if symbols[s] != top_element: return False else: stack.push(s) return not stack
由于我們只需要遍歷符號串一邊,因此算法的時間復雜度為O(n),算法的空間復雜度同樣為O(n)。
給定一鏈表(帶有頭結點) L : L0→L1→…→Ln?,將其重排為:L0→Ln→L1→Ln?1 … 。
例如鏈表中包含 9 個元素,則下圖現實了重排前后的鏈表元素情況:
由于棧的先進后出原則,可以利用棧與原鏈表的配合進行重排,首次按遍歷鏈表,將每個結點入棧;棧中元素的出棧順序為原鏈表結點的逆序,然后交替遍歷鏈表和棧,構建新鏈表。
def reorder_list(L): p = L.head.next if p == None: return L stack = Stack() while p!= None: stack.push(p) p = p.next l = L.head.next from_head = L.head.next from_stack = True while (from_stack and l != stack.peek() or (not from_stack and l != from_head)): if from_stack: from_head = from_head.next l.next = stack.pop() from_stack = False else: l.next = from_head from_stack = True l = l.next l.next = None
該算法的時間復雜度和空間復雜度均為O(n)。
原文鏈接:https://blog.csdn.net/LOVEmy134611/article/details/123327176
相關推薦
- 2023-02-18 Go語言IO輸入輸出底層原理及文件操作API_Golang
- 2022-07-03 pandas選擇或添加列生成新的DataFrame操作示例_python
- 2022-12-22 go語言中GoMock安裝使用詳解_Golang
- 2022-09-13 Go語言實現超時的三種方法實例_Golang
- 2022-04-13 .NET5實現操作注冊表的方法_實用技巧
- 2022-05-28 Nginx實現Nacos反向代理的項目實踐_nginx
- 2022-07-09 Python二分查找+字符串模板+textwrap模塊,_python
- 2022-12-26 C語言實現十六進制轉換為十進制的方法詳解_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支