在 Python 開發中,資料結構的運用至關重要,尤其堆積積、樹和圖更是解決許多複雜問題的利器。heapq 模組提供便捷的堆積積操作方式,能有效管理優先佇列。樹狀結構則適用於表示階層式資料,例如檔案系統或組織架構。圖結構則更擅長於描繪物件之間的關係,常見於社交網路分析、地圖導航等應用。理解這些資料結構的特性和操作方法,能提升程式碼的效率和可讀性。透過 Python 的物件導向特性,可以輕鬆建立和操作這些資料結構,例如利用類別定義節點,並使用列表或集合表示節點間的關係。在圖形結構中,深度優先搜尋(DFS)和廣度優先搜尋(BFS)是兩種常用的遍歷演算法,選擇哪種演算法取決於具體的應用場景。例如,DFS 適合用於尋找特定路徑,而 BFS 則更適合用於尋找最短路徑。
駕馭資料結構:堆積積、樹與圖的 Python 實戰
資料結構是程式設計的根本,掌握它們能讓你在解決複雜問題時更加得心應手。今天,玄貓將帶領大家探討 Python 中三種重要的資料結構:堆積積(Heap)、樹(Tree)和圖(Graph),並結合實際範例,讓你輕鬆掌握它們的應用。
堆積積 (Heap):高效管理優先順序任務
堆積積是一種特殊的樹狀資料結構,最常見的應用就是優先佇列。在 Python 中,heapq 模組提供了堆積積的實作,讓我們可以輕鬆地建立和操作堆積積。
建立堆積積
heapq 模組提供 heapify() 函式,可以將現有的列表轉換成堆積積。
import heapq
myheap = [3, 1, 4, 1, 5, 9, 2, 6, 5]
heapq.heapify(myheap)
print(myheap) # Output: [1, 1, 2, 3, 5, 9, 4, 6, 5]
內容解密:
- 引入 heapq 模組: 首先,我們需要引入 Python 的
heapq模組,這個模組提供了堆積積(heap)資料結構的相關函式。 - 建立列表: 建立一個名為
myheap的列表,其中包含一些數值。這個列表將被轉換為堆積積。 - heapify 轉換: 呼叫
heapq.heapify(myheap)函式。這個函式會將myheap列表轉換成一個最小堆積積(min-heap)。最小堆積積的特性是父節點的值小於或等於其子節點的值。經過heapify處理後,myheap列表的元素會重新排列,以符合堆積積的特性。 - 印出結果: 最後,使用
print(myheap)印出經過heapify處理後的列表。你會看到列表中的元素已經被重新排列,最小的元素被放在了列表的最前面。
從堆積積中取出最小元素
使用 heappop() 函式可以取出堆積積中的最小元素,並同時調整堆積積結構。
import heapq
# 從堆積積中取出最小元素
myheap = [3, 1, 4, 1, 5, 9, 2, 6, 5]
heapq.heapify(myheap)
print(heapq.heappop(myheap)) # Output: 1
print(heapq.heappop(myheap)) # Output: 1
內容解密:
- 引入 heapq 模組: 引入 Python 的
heapq模組,以便使用堆積積相關的函式。 - 建立列表並轉換為堆積積: 建立一個名為
myheap的列表,並使用heapq.heapify(myheap)將其轉換為最小堆積積。 - 取出最小元素: 呼叫
heapq.heappop(myheap)函式。這個函式會從myheap堆積積中取出最小的元素(也就是堆積積頂端的元素),並將堆積積重新調整,以保持堆積積的特性。第一次呼叫heappop時,會取出數值 1。 - 再次取出最小元素: 再次呼叫
heapq.heappop(myheap)函式,這時會取出堆積積中下一個最小的元素。由於列表中有兩個 1,所以第二次呼叫heappop時,也會取出數值 1。 - 印出結果: 使用
print()函式印出每次heappop取出的元素。
向堆積積中加入元素
使用 heappush() 函式可以將新元素加入堆積積中,並自動調整堆積積結構。
import heapq
# 向堆積積中加入元素
myheap = [3, 1, 4, 1, 5, 9, 2, 6, 5]
heapq.heapify(myheap)
heapq.heappush(myheap, 0)
heapq.heappush(myheap, 7)
print(myheap) # Output: [0, 1, 2, 3, 5, 9, 4, 6, 5, 7]
內容解密:
- 引入 heapq 模組: 引入 Python 的
heapq模組,以便使用堆積積相關的函式。 - 建立列表並轉換為堆積積: 建立一個名為
myheap的列表,並使用heapq.heapify(myheap)將其轉換為最小堆積積。 - 加入新元素: 呼叫
heapq.heappush(myheap, 0)將數值 0 加入堆積積中。heappush函式會將新元素插入堆積積,並調整堆積積的結構,以保持最小堆積積的特性。 - 再次加入新元素: 呼叫
heapq.heappush(myheap, 7)將數值 7 加入堆積積中。 - 印出結果: 使用
print(myheap)印出經過插入操作後的堆積積。你會看到新元素已經被正確地插入到堆積積中,與堆積積的結構仍然保持有效。
堆積積的大小
使用 len() 函式可以取得堆積積的大小,也就是堆積積中元素的個數。
import heapq
# 檢查堆積積的大小
myheap = [3, 1, 4, 1, 5, 9, 2, 6, 5]
heapq.heapify(myheap)
print(len(myheap)) # Output: 9
內容解密:
- 引入 heapq 模組: 引入 Python 的
heapq模組,以便使用堆積積相關的函式。 - 建立列表並轉換為堆積積: 建立一個名為
myheap的列表,並使用heapq.heapify(myheap)將其轉換為最小堆積積。 - 檢查堆積積大小: 呼叫
len(myheap)函式,這個函式會回傳myheap列表中元素的個數,也就是堆積積的大小。 - 印出結果: 使用
print()函式印出堆積積的大小。
樹 (Tree):組織階層式資料
樹是一種階層式的資料結構,由節點和邊組成。每個樹都有一個根節點,其他節點都是根節點的子節點。樹在電腦科學中被廣泛應用,例如檔案系統、組織結構等。
建立樹
在 Python 中,可以使用類別來建立樹的節點,並使用物件來表示樹的結構。
class Node:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
# 建立一個樹
root = Node(1)
root.left = Node(2)
root.right = Node(3)
root.left.left = Node(4)
root.left.right = Node(5)
內容解密:
- 定義 Node 類別: 首先,定義一個名為
Node的類別,這個類別代表樹的節點。每個節點都有三個屬性:data:儲存節點的資料。left:指向左子節點的指標。right:指向右子節點的指標。
- 初始化節點: 在
Node類別的__init__方法中,我們初始化節點的資料,並將left和right指標設為None,表示這個節點一開始沒有子節點。 - 建立樹的結構: 接著,我們建立一個樹的結構。首先,建立一個根節點
root,其資料為 1。然後,建立根節點的左子節點root.left,其資料為 2,以及右子節點root.right,其資料為 3。 - 建立更深層的節點: 繼續建立左子節點的子節點
root.left.left,其資料為 4,以及root.left.right,其資料為 5。這樣就建立了一個簡單的樹狀結構。
走訪樹
樹有多種走訪方式,常見的有以下三種:
- 中序走訪(Inorder Traversal): 先走訪左子樹,再走訪根節點,最後走訪右子樹。
- 前序走訪(Preorder Traversal): 先走訪根節點,再走訪左子樹,最後走訪右子樹。
- 後序走訪(Postorder Traversal): 先走訪左子樹,再走訪右子樹,最後走訪根節點。
class Node:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
# 建立一個樹
root = Node(1)
root.left = Node(2)
root.right = Node(3)
root.left.left = Node(4)
root.left.right = Node(5)
def inorder(node):
if node is not None:
inorder(node.left)
print(node.data)
inorder(node.right)
# 中序走訪樹
inorder(root)
def preorder(node):
if node is not None:
print(node.data)
preorder(node.left)
preorder(node.right)
# 前序走訪樹
preorder(root)
def postorder(node):
if node is not None:
postorder(node.left)
postorder(node.right)
print(node.data)
# 後序走訪樹
postorder(root)
內容解密:
- Inorder (中序走訪):
- 遞迴呼叫: 首先,遞迴地呼叫
inorder(node.left),也就是先走訪目前節點的左子樹。 - 印出節點資料: 當左子樹走訪完畢後,印出目前節點的資料
print(node.data)。 - 遞迴呼叫: 最後,遞迴地呼叫
inorder(node.right),也就是走訪目前節點的右子樹。 - 走訪順序: 左 -> 根 -> 右
- 遞迴呼叫: 首先,遞迴地呼叫
- Preorder (前序走訪):
- 印出節點資料: 首先,印出目前節點的資料
print(node.data)。 - 遞迴呼叫: 接著,遞迴地呼叫
preorder(node.left),也就是走訪目前節點的左子樹。 - 遞迴呼叫: 最後,遞迴地呼叫
preorder(node.right),也就是走訪目前節點的右子樹。 - 走訪順序: 根 -> 左 -> 右
- 印出節點資料: 首先,印出目前節點的資料
- Postorder (後序走訪):
- 遞迴呼叫: 首先,遞迴地呼叫
postorder(node.left),也就是先走訪目前節點的左子樹。 - 遞迴呼叫: 接著,遞迴地呼叫
postorder(node.right),也就是走訪目前節點的右子樹。 - 印出節點資料: 最後,印出目前節點的資料
print(node.data)。 - 走訪順序: 左 -> 右 -> 根
- 遞迴呼叫: 首先,遞迴地呼叫
在樹中尋找元素
可以使用遞迴函式在樹中尋找特定的元素。
class Node:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
# 建立一個樹
root = Node(1)
root.left = Node(2)
root.right = Node(3)
root.left.left = Node(4)
root.left.right = Node(5)
def find(node, data):
if node is None:
return False
elif node.data == data:
return True
elif data < node.data:
return find(node.left, data)
else:
return find(node.right, data)
# 在樹中尋找元素
print(find(root, 2)) # Output: True
print(find(root, 6)) # Output: False
內容解密:
- 基本情況:
- 節點為空: 首先檢查目前節點
node是否為None。如果節點為空,表示已經走訪到樹的末端,與沒有找到目標資料,因此回傳False。 - 找到目標資料: 接著檢查目前節點的資料
node.data是否等於目標資料data。如果相等,表示找到了目標資料,因此回傳True。
- 節點為空: 首先檢查目前節點
- 遞迴搜尋:
- 目標資料較小: 如果目標資料
data小於目前節點的資料node.data,表示目標資料可能位於左子樹中。因此,遞迴呼叫find(node.left, data),在左子樹中繼續搜尋。 - 目標資料較大: 如果目標資料
data大於目前節點的資料node.data,表示目標資料可能位於右子樹中。因此,遞迴呼叫find(node.right, data),在右子樹中繼續搜尋。
- 目標資料較小: 如果目標資料
圖 (Graph):表示物件間的關係
圖是一種用來表示物件之間關係的資料結構,由節點(或稱為頂點)和邊組成。圖在社交網路、地圖導航等領域有廣泛的應用。
建立圖
在 Python 中,可以使用類別來建立圖的節點,並使用列表來表示節點之間的連線關係。
class Node:
def __init__(self, data):
self.data = data
self.neighbors = []
# 建立一個圖
A = Node('A')
B = Node('B')
C = Node('C')
D = Node('D')
E = Node('E')
F = Node('F')
G = Node('G')
H = Node('H')
A.neighbors = [B, C, D]
B.neighbors = [A, E]
C.neighbors = [A, F]
D.neighbors = [A, G, H]
E.neighbors = [B]
F.neighbors = [C]
G.neighbors = [D, H]
H.neighbors = [D, G]
內容解密:
- 定義 Node 類別: 首先,定義一個名為
Node的類別,這個類別代表圖的節點。每個節點都有兩個屬性:data:儲存節點的資料。neighbors:一個列表,儲存與目前節點相鄰的節點。
- 初始化節點: 在
Node類別的__init__方法中,我們初始化節點的資料,並將neighbors列表設為空列表,表示這個節點一開始沒有相鄰的節點。 - 建立圖的節點: 接著,我們建立圖的各個節點,例如 A、B、C 等等,每個節點都使用
Node類別建立。 - 建立節點之間的連線關係: 建立節點之間的連線關係。例如,
A.neighbors = [B, C, D]表示節點 A 與節點 B、C、D 相鄰。
走訪圖
圖有多種走訪方式,常見的有以下兩種:
- 深度優先搜尋(Depth-First Search, DFS): 從起始節點開始,沿著圖的深度方向走訪,直到無法繼續深入為止,然後回溯到上一個節點,繼續走訪其他分支。
- 廣度優先搜尋(Breadth-First Search, BFS): 從起始節點開始,先走訪所有與起始節點相鄰的節點,然後再走訪與這些相鄰節點相鄰的節點,以此類別推。
class Node:
def __init__(self, data):
self.data = data
self.neighbors = []
# 建立一個圖
A = Node('A')
B = Node('B')
C = Node('C')
D = Node('D')
E = Node('E')
F = Node('F')
G = Node('G')
H = Node('H')
A.neighbors = [B, C, D]
B.neighbors = [A, E]
C.neighbors = [A, F]
D.neighbors = [A, G, H]
E.neighbors = [B]
F.neighbors = [C]
G.neighbors = [D, H]
H.neighbors = [D, G]
def dfs(node, visited):
visited.add(node)
print(node.data)
for neighbor in node.neighbors:
if neighbor not in visited:
dfs(neighbor, visited)
# 深度優先搜尋圖
visited = set()
dfs(A, visited)
def bfs(node):
visited = set()
queue = [node]
visited.add(node)
while queue:
curr_node = queue.pop(0)
print(curr_node.data)
for neighbor in curr_node.neighbors:
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
# 廣度優先搜尋圖
bfs(A)
內容解密:
- DFS (深度優先搜尋):
- 標記為已走訪: 首先,將目前節點
node加入visited集合中,表示這個節點已經被走訪過了。 - 印出節點資料: 接著,印出目前節點的資料
print(node.data)。 - 走訪相鄰節點: 走訪目前節點的所有相鄰節點
neighbor。 - 遞迴呼叫: 如果相鄰節點尚未被走訪過(也就是
neighbor not in visited),則遞迴呼叫dfs(neighbor, visited),繼續以深度優先的方式走訪相鄰節點。
- 標記為已走訪: 首先,將目前節點
- BFS (廣度優先搜尋):
- 初始化:
- 建立一個
visited集合,用來記錄已經走訪過的節點。 - 建立一個佇列
queue,並將起始節點node加入佇列中。 - 將起始節點標記為已走訪。
- 建立一個
- 迴圈:
- 當佇列不為空時,重複執行以下步驟:
- 從佇列中取出第一個節點
curr_node。 - 印出節點資料。
- 走訪目前節點的所有相鄰節點
neighbor。 - 如果相鄰節點尚未被走訪過,則將其加入佇列,並標記為已走訪。
- 初始化:
在圖中尋找路徑
可以使用遞迴函式在圖中尋找兩個節點之間的路徑。
class Node:
def __init__(self, data):
self.data = data
self.neighbors = []
# 建立一個圖
A = Node('A')
B = Node('B')
C = Node('C')
D = Node('D')
E = Node('E')
F = Node('F')
G = Node('G')
H = Node('H')
A.neighbors = [B, C, D]
B.neighbors = [A, E]
C.neighbors = [A, F]
D.neighbors = [A, G, H]
E.neighbors = [B]
F.neighbors = [C]
G.neighbors = [D, H]
H.neighbors = [D, G]
def find_path(start_node, end_node, visited, path):
visited.add(start_node)
path.append(start_node)
if start_node == end_node:
return path
for neighbor in start_node.neighbors:
if neighbor not in visited:
result = find_path(neighbor, end_node, visited, path)
if result:
return result
path.pop()
# 在圖中尋找路徑
visited = set()
path = []
result = find_path(A, H, visited, path)
print([node.data for node in result])
內容解密:
- 標記與記錄:
- 將目前節點
start_node標記為已走訪,加入visited集合中。 - 將目前節點加入路徑
path列表中。
- 將目前節點
- 找到目標:
- 如果目前節點等於目標節點
end_node,表示已經找到路徑,直接回傳path。
- 如果目前節點等於目標節點
- 遞迴搜尋:
- 走訪目前節點的所有相鄰節點
neighbor。 - 如果相鄰節點尚未被走訪過,則遞迴呼叫
find_path(neighbor, end_node, visited, path),繼續搜尋路徑。 - 如果遞迴呼叫找到路徑(也就是
result不為空),則直接回傳result。
- 走訪目前節點的所有相鄰節點
- 回溯:
- 如果所有相鄰節點都走訪過,但仍然沒有找到路徑,表示目前路徑是錯誤的,需要回溯。
- 將目前節點從路徑
path列表中移除。
玄貓今天介紹了 Python 中三種重要的資料結構:堆積積、樹和圖。希望透過這些實戰範例,能幫助你更深入地理解它們的特性和應用場景。掌握這些資料結構,將使你在程式設計的道路上更上一層樓。
程式碼尋徑:Python 圖形結構的優雅解法
在圖形結構中尋找特定節點間的路徑,是許多演算法的核心問題。以下程式碼延續前文,展示如何使用 Python 實作尋徑功能:
class Node:
def __init__(self, data):
self.data = data
self.neighbors = []
def add_neighbor(self, node):
self.neighbors.append(node)
def find_path(start_node, end_node, visited, path):
visited.add(start_node)
path.append(start_node)
if start_node == end_node:
return path
for neighbor in start_node.neighbors:
if neighbor not in visited:
result = find_path(neighbor, end_node, visited, path)
if result:
return result
path.pop()
return None
# 建立圖形結構
A = Node('A')
B = Node('B')
C = Node('C')
D = Node('D')
E = Node('E')
F = Node('F')
G = Node('G')
H = Node('H')
A.add_neighbor(B)
A.add_neighbor(C)
B.add_neighbor(D)
C.add_neighbor(E)
D.add_neighbor(F)
E.add_neighbor(G)
F.add_neighbor(H)
# 尋找從 A 到 H 的路徑
visited = set()
path = []
result = find_path(A, H, visited, path)
if result:
print('Path found:', [node.data for node in result])
else:
print('Path not found')
內容解密
- Node 類別:定義圖形中的節點,包含資料和相鄰節點列表。
- find_path 函式:使用遞迴方式尋找路徑,
visited集合追蹤已存取節點,path列表記錄當前路徑。 - 圖形建立:手動建立一個圖形,節點間的連線使用
add_neighbor方法。 - 路徑尋找:呼叫
find_path函式,如果找到路徑則印出,否則顯示未找到。
玄貓觀點:圖形結構的應用與演算法選擇
圖形結構在現實世界中有廣泛應用,例如社交網路、地圖導航和網路路由等。選擇合適的圖形遍歷演算法(如 DFS 或 BFS)取決於具體應用場景和需求。