說明:
本文主要展示Python實現的幾種常用數據結構:順序表、鏈表、棧和隊列。
附有實現代碼。
來源主要參考網絡文章。
一、順序表
1、順序表的結構
一個順序表的完整信息包括兩部分,一部分是表中元素集合,另一部分是為實現正確操作而需記錄的信息,即有關表的整體情況的信息,這部分信息主要包括元素存儲區的容量和當前表中已有的元素個數兩項。
2、順序表的兩種基本實現方式
圖a 為一體式結構,存儲表信息的單元與元素存儲區以連續的方式安排在一塊存儲區里,兩部分數據的整體形成一個完整的順序表對象。一體式結構整體性強,易於管理。但是由於數據元素存儲區域是表對象的一部分,順序表創建后,元素存儲區就固定了。
圖b 為分離式結構,表對象里只保存與整個表有關的信息(容量和元素個數),實際數據元素存放在另一個獨立的元素存儲區里,通過鏈接與基本表對象關聯。
3、元素存儲區替換
一體式結構由於順序表信息區與數據區聯系存儲在一起,所以若想更換數據區,則只能整體搬遷,即整個順序表對象(指存儲順序表的結構信息的區域)改變了。
分離式結構若想更換數據區,只需將表信息區中的數據區鏈接地址更新即可,而該順序表對象不變。
4、元素存儲區擴充及其策略
分離式結構的順序表,如想將數據區更換為存儲空間更大的區域,則可以在不改變表對象的前提下對其數據存儲區進行了擴充,所有使用這個表的地方都不必修改。只要程序的運行環境(計算機系統)還有空閑存儲,這種表結構就不會因為滿了而導致操作無法進行。人們把采用這種技術實現的順序表稱為動態順序表,因為其容量可以在使用中動態變化。
擴充的兩種策略:
》每次擴充增加固定數目的存儲位置,如每次擴充10個元素位置,這種策略可稱為線性增長。
(特點:節省空間,但是擴充操作頻繁,操作次數多)
》每次擴充容量加倍,如每次擴充增加一倍存儲空間。
(特點:減少了擴充操作的執行次數,但可能會浪費空間資源。以空間換時間,推薦的方式)
》Python的官方實現中,list 實現采用了如下的策略:在建立空表(或很小的表)時,系統分配一塊能容納8個元素的存儲區;在執行插入操作(insert或append)時,如果元素存儲區滿就換一塊4倍大的存儲區。但如果此時的表已經很大(目前閥值為50000),則改變策略,采用加一倍的方法。引入這種改變策略的方式,是為了避免出現過多的空閑的存儲位置。
5、順序表的操作
增加元素,下圖為順序表增加元素的三種方式:
a、尾端加入元素,時間復雜度為 O(1)
b、非保序的加入元素(不常見)沒時間復雜度為 O(1)
c、保序的元素加入,時間復雜度為 O(n)
刪除元素,下圖為順序表刪除元素的三種方式:
a、刪除表尾元素,時間復雜度為 O(1)
b、非保序的元素刪除(不常見),時間復雜度為 O(1)
c、保序的元素刪除,時間復雜度為 O(n)
6、Python 中的順序表
Python中的 list 和 tuple 兩種類型采用了順序表的實現技術,具有前面討論的順序表的所有性質。
tuple是不可變類型,即不變的順序表,因此不支持改變其內部狀態任何操作,而其他方面,則與list的性質類似。
list的基本實現技術:
Python表中類型list就是一種元素個數可變的線性表,可以加入和刪除元素,並在各種操作維持已有元素順序(即保序),而且還具有以下行為特征:
》基於下標(位置)的高效元素訪問和更新,時間復雜度應該是 O(1);
為滿足該特征,應該采用順序表技術,表中元素保存在一塊連續的存儲區中。
》允許任意加入元素,而且在不斷加入元素的過程中,表對象的標識(函數id得到的值)不變
為滿足該特征,就必須能更換元素存儲區,並且為保證更換存儲區時list對象的標識id不變,只能采用分離式實現技術。
在Python官方實現中,list就是一種采用分離式技術實現的動態順序表。這就是為什么用list.append(x)(或list.insert(len(list), x), 即尾部插入)比在指定位置插入元素效率高的原因。
二、鏈表
相對於順序表,鏈表結構可以充分利用計算機內存空間,實現靈活的內存動態管理,因為順序表的結構需要預先知道數據大小來申請連續的存儲空間,而在進行擴充時又需要進行數據的搬遷。
鏈表(Linked list)是一種常見的基礎數據結構,是一種線性表,但是不像順序表一樣連續存儲數據,而是每一個結點(數據存儲單元)里存放下一個結點的信息(即地址):
1、單向鏈表
單向鏈表也叫單鏈表,是表中最簡單的一種形式,它的每個節點包含兩個域,一個信息域(元素域)和一個鏈接域。這個鏈接指向鏈表中的下一個節點,而最后一個節點的鏈接域則指向一個空值。
表中元素elem用來存放具體的數據。
鏈接域next用來存放下一個節點的位置(Python中的標識)。
變量p指向鏈表的頭節點(首節點)的位置,從p出發能找到表中的任意節點。
單鏈表的操作:
is_empty():鏈表是否為空
length():鏈表長度
travel():遍歷整個鏈表
add(item):鏈表頭部添加元素
append(item):鏈表尾部添加元素
insert(pos, item):指定位置添加元素
remove(item):刪除節點
search(item):查找節點是否存在
代碼實現:
1 # coding=utf-8 2 # 單鏈表的實現 3 4 5 class SingleNode: 6 """單鏈表的節點""" 7 def __init__(self, item): 8 # item存放數據元素 9 self.item = item 10 # 下一個節點 11 self.next = None 12 13 def __str__(self): 14 return str(self.item) 15 16 17 class SingleLinkList: 18 """單鏈表""" 19 def __init__(self): 20 self._head = None 21 22 def is_empty(self): 23 """判斷鏈表是否為空""" 24 return self._head is None 25 26 def length(self): 27 """獲取鏈表長度""" 28 cur = self._head 29 count = 0 30 while cur is not None: 31 count += 1 32 # 將cur后移,指向下一個節點 33 cur = cur.next 34 return count 35 36 def travel(self): 37 """遍歷鏈表""" 38 cur = self._head 39 while cur is not None: 40 print(cur.item) 41 cur = cur.next 42 print("") 43 44 def add(self, item): 45 """鏈表頭部添加元素""" 46 node = SingleNode(item) 47 48 node.next = self._head 49 self._head = node 50 51 def append(self, item): 52 """鏈表尾部添加元素""" 53 node = SingleNode(item) 54 55 if self.is_empty(): 56 self._head = node 57 else: 58 cur = self._head 59 while cur.next is not None: 60 cur = cur.next 61 62 # 此時cur指向鏈表最后一個節點,即 next = None 63 cur.next = node 64 65 def insert(self, pos, item): 66 """指定位置添加元素""" 67 # 若指定位置pos為第一個元素之前,則執行頭部插入 68 if pos <= 0: 69 self.add(item) 70 71 # 若指定位置超過鏈表尾部,則執行尾部插入 72 elif pos > (self.length() - 1): 73 self.append(item) 74 75 # 找到指定位置 76 else: 77 node = SingleNode(item) 78 cur = self._head 79 cur_pos = 0 80 while cur.next is not None: 81 # 獲取需要插入位置的上一個節點 82 if pos - 1 == cur_pos: 83 node.next = cur.next 84 cur.next = node 85 cur = cur.next 86 cur_pos += 1 87 88 def remove(self, item): 89 """刪除節點""" 90 cur = self._head 91 while cur is not None: 92 if cur.next.item == item: 93 cur.next = cur.next.next 94 break 95 cur = cur.next 96 97 def search(self, item): 98 """查找節點是否存在""" 99 cur = self._head 100 count = 0 101 while cur is not None: 102 if cur.item == item: 103 return count 104 cur = cur.next 105 count += 1 106 107 # 找不到元素 108 if count == self.length(): 109 count = -1 110 return count 111 112 113 if __name__ == "__main__": 114 ll = SingleLinkList() 115 ll.add(1) # 1 116 ll.add(2) # 2 1 117 ll.append(3) # 2 1 3 118 ll.insert(2, 4) # 2 1 4 3 119 print("length:", ll.length()) # 4 120 ll.travel() # 2 1 4 3 121 print("search(3):", ll.search(3)) # 3 122 print("search(5):", ll.search(5)) # -1 123 ll.remove(1) 124 print("length:", ll.length()) # 3 125 ll.travel() # 2 4 3
鏈表與順序表的對比:
鏈表失去了順序表隨機讀取的優點,同時鏈表由於增加了節點的指針域,空間開銷比較大,但對存儲空間的使用要相對靈活。
鏈表與順序表的各種操作復雜度如下所示:
操作 | 鏈表 | 順序表 |
訪問元素 | O(n) | O(1) |
在頭部插入/刪除 | O(1) | O(n) |
在尾部安插入/刪除 | O(n) | O(1) |
在中間插入/刪除 | O(n) | O(n) |
注意:雖然表面看起來復雜度都是 O(n),但是鏈表和順序表在插入和刪除時進行的是完全不同的操作。鏈表的主要耗時操作是遍歷查找,刪除和插入操作本身的復雜度是O(1)。順序表查找很快,主要耗時的操作是拷貝覆蓋。因為除了目標元素在尾部的特殊情況,順序表進行插入和刪除時需要對操作點之后所有元素進行前后移位操作,只能通過拷貝和覆蓋方法進行。
2、單向循環鏈表
單鏈表的一個變形是單向循環鏈表,鏈表中最后一個節點的next域不再為None,而是指向鏈表的頭結點。
基本操作和單鏈表基本一樣,實現代碼如下:
1 # coding=utf-8 2 # 單向循環鏈表 3 4 5 class Node: 6 """節點""" 7 def __init__(self, item): 8 self.item = item 9 self.next = None 10 11 def __str__(self): 12 return str(self.item) 13 14 15 class SinCycLinkedList: 16 """單向循環鏈表""" 17 def __init__(self): 18 self._head = None 19 20 def is_empty(self): 21 """判斷鏈表是否為空""" 22 return self._head is None 23 24 def length(self): 25 """鏈表長度""" 26 if self.is_empty(): 27 return 0 28 count = 1 29 cur = self._head 30 while cur.next != self._head: 31 # print("cur", cur.item) 32 count += 1 33 cur = cur.next 34 return count 35 36 def travel(self): 37 """遍歷""" 38 if self.is_empty(): 39 return 40 41 cur = self._head 42 print(cur.item) 43 while cur.next != self._head: 44 cur = cur.next 45 print(cur.item) 46 47 def add(self, item): 48 """在頭部添加一個節點""" 49 node = Node(item) 50 if self.is_empty(): 51 self._head = node 52 node.next = self._head 53 else: 54 node.next = self._head 55 cur = self._head 56 while cur.next != self._head: 57 cur = cur.next 58 59 cur.next = node 60 self._head = node 61 62 def append(self, item): 63 """在尾部添加一個節點""" 64 node = Node(item) 65 if self.is_empty(): 66 self._head = node 67 node.next = self._head 68 else: 69 cur = self._head 70 # print(type(cur), cur.item, cur.next) 71 while cur.next != self._head: 72 cur = cur.next 73 74 # print(cur.item) 75 cur.next = node 76 node.next = self._head 77 78 def insert(self, pos, item): 79 """指定位置pos添加節點""" 80 if pos <= 0: 81 self.add(item) 82 elif pos > (self.length() - 1): 83 self.append(item) 84 else: 85 node = Node(item) 86 cur = self._head 87 cur_pos = 0 88 while cur.next != self._head: 89 if (pos - 1) == cur_pos: 90 node.next = cur.next 91 cur.next = node 92 break 93 cur_pos += 1 94 cur = cur.next 95 96 def remove(self, item): 97 """刪除一個節點""" 98 if self.is_empty(): 99 return 100 101 pre = self._head 102 # 刪除首節點 103 if pre.item == item: 104 cur = pre 105 while cur.next != self._head: 106 cur = cur.next 107 108 cur.next = pre.next # 刪除首節點(跳過該節點) 109 self._head = pre.next # 重新指定首節點 110 111 # 刪除其他的節點 112 else: 113 cur = pre 114 while cur.next != self._head: 115 if cur.next.item == item: 116 cur.next = cur.next.next 117 cur = cur.next 118 119 def search(self, item): 120 """查找節點是否存在""" 121 if self.is_empty(): 122 return -1 123 124 cur_pos = 0 125 cur = self._head 126 if cur.item == item: 127 return cur_pos 128 129 while cur.next != self._head: 130 if cur.item == item: 131 return cur_pos 132 cur_pos += 1 133 cur = cur.next 134 135 if cur_pos == self.length() - 1: 136 return -1 137 138 139 if __name__ == "__main__": 140 ll = SinCycLinkedList() 141 ll.add(1) # 1 142 ll.add(2) # 2 1 143 # ll.travel() 144 ll.append(3) # 2 1 3 145 ll.insert(2, 4) # 2 1 4 3 146 ll.insert(4, 5) # 2 1 4 3 5 147 ll.insert(0, 6) # 6 2 1 4 3 5 148 print("length:", ll.length()) # 6 149 ll.travel() # 6 2 1 4 3 5 150 print("search(3)", ll.search(3)) # 4 151 print("search(7)", ll.search(7)) # -1 152 print("search(6)", ll.search(6)) # 0 153 print("remove(1)") 154 ll.remove(1) 155 print("length:", ll.length()) # 6 2 4 3 5 156 print("remove(6)") 157 ll.remove(6) 158 ll.travel()
3、雙向鏈表
一種更復雜的鏈表是 "雙向鏈表" 或 "雙面鏈表"。每個節點有兩個鏈接:一個指向前一個節點,當次節點為第一個節點時,指向空值;而另一個指向下一個節點,當此節點為最后一個節點時,指向空值。
基本操作和單鏈表一樣,不同的實現,代碼如下:
1 # coding=utf-8 2 # 雙向鏈表 3 4 5 class Node: 6 """節點""" 7 def __init__(self, item): 8 self.item = item 9 self.prev = None 10 self.next = None 11 12 13 class DLinkList: 14 """雙向鏈表""" 15 def __init__(self): 16 self._head = None 17 18 def is_empty(self): 19 """判斷鏈表是否為空""" 20 return self._head is None 21 22 def length(self): 23 """獲取鏈表長度""" 24 if self.is_empty(): 25 return 0 26 else: 27 cur = self._head 28 count = 1 29 while cur.next is not None: 30 count += 1 31 cur = cur.next 32 33 return count 34 35 def travel(self): 36 """遍歷鏈表""" 37 print("↓↓" * 10) 38 if self.is_empty(): 39 print("") 40 41 else: 42 cur = self._head 43 print(cur.item) 44 while cur.next is not None: 45 cur = cur.next 46 print(cur.item) 47 print("↑↑" * 10) 48 49 def add(self, item): 50 """鏈表頭部添加節點""" 51 node = Node(item) 52 if self.is_empty(): 53 self._head = node 54 else: 55 cur = self._head 56 57 node.next = cur 58 cur.prev = node 59 self._head = node 60 61 def append(self, item): 62 """鏈表尾部添加節點""" 63 node = Node(item) 64 if self.is_empty(): 65 self._head = node 66 else: 67 cur = self._head 68 # 遍歷找到最后一個節點 69 while cur.next is not None: 70 cur = cur.next 71 72 # 在尾節點添加新的節點 73 cur.next = node 74 node.prev = cur 75 76 def insert(self, pos, item): 77 """指定位置添加""" 78 # 頭部添加 79 if pos <= 0: 80 self.add(item) 81 82 # 尾部添加 83 elif pos > (self.length() - 1): 84 self.append(item) 85 86 # 其他位置添加 87 else: 88 node = Node(item) 89 90 cur = self._head 91 cur_pos = 0 92 while cur.next is not None: 93 if cur_pos == (pos - 1): 94 # 與下一個節點互相指向 95 node.next = cur.next 96 cur.next.prev = node 97 # 與上一個節點互相指向 98 cur.next = node 99 node.prev = cur 100 cur_pos += 1 101 cur = cur.next 102 103 def remove(self, item): 104 """刪除節點""" 105 if self.is_empty(): 106 return 107 else: 108 cur = self._head 109 # 刪除首節點 110 if cur.item == item: 111 self._head = cur.next 112 cur.next.prev = None 113 114 # 刪除其他節點 115 else: 116 while cur.next is not None: 117 if cur.item == item: 118 # 刪除之前:1 ←→ [2] ←→ 3 119 # 刪除之后:1 ←→ 3 120 cur.prev.next = cur.next 121 cur.next.prev = cur.prev 122 cur = cur.next 123 124 # 刪除尾節點 125 if cur.item == item: 126 cur.prev.next = None 127 128 129 def search(self, item): 130 """查找節點是否存在""" 131 if self.is_empty(): 132 return -1 133 else: 134 cur = self._head 135 cur_pos = 0 136 while cur.next is not None: 137 if cur.item == item: 138 return cur_pos 139 140 cur_pos += 1 141 cur = cur.next 142 143 if cur_pos == (self.length() - 1): 144 return -1 145 146 147 if __name__ == "__main__": 148 ll = DLinkList() 149 ll.add(1) # 1 150 ll.add(2) # 2 1 151 ll.append(3) # 2 1 3 152 ll.insert(2, 4) # 2 1 4 3 153 ll.insert(4, 5) # 2 1 4 3 5 154 ll.insert(0, 6) # 6 2 1 4 3 5 155 print("length:", ll.length()) # 6 156 ll.travel() # 6 2 1 4 3 5 157 print("search(3)", ll.search(3)) 158 print("search(4)", ll.search(4)) 159 print("search(10)", ll.search(10)) 160 ll.remove(1) 161 print("length:", ll.length()) 162 ll.travel() 163 print("刪除首節點 remove(6):") 164 ll.remove(6) 165 ll.travel() 166 print("刪除尾節點 remove(5):") 167 ll.remove(5) 168 ll.travel()
三、棧
棧(stack),也稱為堆棧,是一種容器,可存入數據元素、訪問元素、刪除元素,它的特點在於只能允許在容器的一端(稱為棧頂端指標:top)進行加入數據(push)和輸出數據(pop)的運算。沒有了位置概念,保證任何時候可以訪問、刪除的元素都是此前最后存入的那個元素,確定了一種默認的訪問順序。
由於棧數據結構只允許在一端進行操作,因為按照后進先出(LIFO,Last In First Out)的原理運作。
棧可以用順序表實現,也可以用鏈表實現。
1、棧的操作:
Stack():創建一個新的空棧
push(item):添加一個新的元素item到棧頂
pop():彈出棧頂元素
peek():返回棧頂元素
is_empty():判斷棧是否為空
size():返回棧的元素個數
2、代碼實現
1 # coding=utf-8 2 3 4 class Stack: 5 """棧""" 6 def __init__(self): 7 self.items = [] 8 9 def is_empty(self): 10 """判斷是否為空""" 11 return self.items == [] 12 13 def push(self, item): 14 """加入元素""" 15 self.items.append(item) 16 17 def pop(self): 18 """彈出元素""" 19 return self.items.pop() 20 21 def peek(self): 22 """返回棧頂元素""" 23 return self.items[len(self.items) - 1] 24 25 def size(self): 26 """返回棧的元素個數""" 27 return len(self.items) 28 29 30 if __name__ == "__main__": 31 stack = Stack() 32 stack.push("hello") 33 stack.push("world") 34 stack.push("stack") 35 print(stack.size()) # 3 36 print(stack.peek()) # stack 37 print(stack.pop()) # stack 38 print(stack.pop()) # world 39 print(stack.pop()) # hello
四、隊列
隊列(queue)是只允許在一端進行插入操作,而在另一端進行刪除操作的線性表。
隊列是一種先進先出(FIFO,First In First Out)的線性表。允許插入的一端為隊尾,允許刪除的一端為隊頭。隊列不允許在中間部位進行操作!假設隊列 q=(a1, a2, ,..., an),那么a1就是隊頭元素,而an是隊尾元素。這樣在刪除時,總是從a1開始,而插入時,總是在隊列最后。
1、隊列的實現(同棧一樣,隊列也可以用順序表或者鏈表實現):
隊列的操作:
Queue():創建一個空的隊列
enqueue(item):往隊列中添加一個item元素
dequeue():從隊列頭部刪除一個元素
is_empty():判斷一個隊列是否為空
size():返回隊列的大小
1 # coding=utf-8 2 3 4 class Queue: 5 """隊列""" 6 def __init__(self): 7 self.items = [] 8 9 def is_empty(self): 10 return self.items == [] 11 12 def enqueue(self, item): 13 """添加元素""" 14 self.items.insert(0, item) 15 16 def dequeue(self): 17 """從隊列頭部刪除一個元素""" 18 return self.items.pop() 19 20 def size(self): 21 return len(self.items) 22 23 24 if __name__ == "__main__": 25 q = Queue() 26 q.enqueue("hello") 27 q.enqueue("world") 28 q.enqueue("queue") 29 print(q.size()) 30 print(q.dequeue()) # hello 31 print(q.dequeue()) # world 32 print(q.dequeue()) # queue
2、雙端隊列的實現
雙端隊列(deque,全名 double-ended queue),是一種具有隊列和棧的性質的數據結構。
雙端隊列中的元素可以從兩端彈出,其限定插入和刪除操作在表的兩端進行。雙端隊列可以在隊列任意一端入隊和出隊。
操作:
Deque():創建一個空的雙端隊列
add_front(item):從隊頭加入一個item元素
add_rear(item):從隊尾加入一個item元素
remove_front():從隊頭刪除一個元素
remove_rear():從隊尾刪除一個元素
is_empty():判斷雙端隊列是否為空
size():返回隊列的大小
1 # coding=utf-8 2 3 4 class Deque: 5 """雙端隊列""" 6 def __init__(self): 7 self.items = [] 8 9 def add_front(self, item): 10 """從隊頭加入一個元素""" 11 self.items.insert(0, item) 12 13 def add_rear(self, item): 14 """從隊尾加入一個元素""" 15 self.items.append(item) 16 17 def remove_front(self): 18 """從隊頭刪除一個元素""" 19 return self.items.pop(0) 20 21 def remove_rear(self): 22 """從隊尾刪除一個元素""" 23 return self.items.pop() 24 25 def is_empty(self): 26 """是否為空""" 27 return self.items == [] 28 29 def size(self): 30 """隊列長度""" 31 return len(self.items) 32 33 34 if __name__ == "__main__": 35 deque = Deque() 36 deque.add_front(1) 37 deque.add_front(2) 38 deque.add_rear(3) 39 deque.add_rear(4) 40 print(deque.size()) # 4 41 print(deque.remove_front()) # 2 42 print(deque.remove_front()) # 1 43 print(deque.remove_rear()) # 4 44 print(deque.remove_rear()) # 3