前言
本文內容講解幾種常見的非線性數據結構(樹,字典樹,堆,圖,並查集)的概念,功能及其實現。
樹
樹 (tree) 是一種非常高效的非線性存儲結構。樹,自然中的樹有根,有葉子,對應在數據結構中的樹就是根節點、葉子節點。同一層的節點叫兄弟節點,鄰近不同層的叫父子節點。
樹又分門別類,分爲二叉樹、滿二叉樹和完全二叉樹
二叉樹:每個節點都至多有二個子節點的樹;
滿二叉樹:在二叉樹的基礎上,除了葉子節點外,每個節點都有左右兩個子節點,這種二叉樹叫做滿二叉樹;
完全二叉樹:對於一顆二叉樹,假設其深度爲d(d>1)。除第d層外的所有節點構成滿二叉樹,且第d層所有節點從左向右連續地緊密排列,這樣的二叉樹被稱爲完全二叉樹;
-
樹的深度:一棵樹中節點的最大深度就是樹的深度,也稱爲高度
-
節點深度:對任意節點x,x節點的深度表示爲根節點到x節點的路徑長度。所以根節點深度爲0,第二層節點深度爲1,以此類推
python實現二叉樹
class Node():
def __init__(self,data):
self.data = data #相應的元素數據
self.left = None #左子節點
self.right = None #右級節點
#當使用print輸出對象的時候,只要自己定義了__str__(self)方法,那麼就會打印在這個方法中return的數據
#__str__方法需要返回一個字符串,當做這個對象的描寫
def __str__(self):
return str(self.data)
#二叉樹的實現邏輯
class Tree():
def __init__(self):
#根節點定義爲 root 永不刪除
self.root = Node("root")
#添加節點到二叉樹
def add(self,data):
node = Node(data)
#如果根節點不存在,插入的節點則爲根節點
if self.root is None:
self.root = node
else:
#先在 q 列表中,添加二叉樹的根節點
q = [self.root]
while True:
#先把root節點取出來(此時q列表爲空了),判斷左右子節點爲不爲空,若有一個爲空則插入,插入成功直接return結束循環
#若都不爲空,則把root的左右子節點依次添加到q中,再按同樣的邏輯依次從root的左右子節點找位置插入,插入成功直接return結束循環
pop_node = q.pop(0)
#左子樹爲空則將點添加到左子樹
if pop_node.left == None:
pop_node.left = node
return
#右子樹爲空則將點添加到右子樹
elif pop_node.right == None:
pop_node.right = node
return
else:
q.append(pop_node.left)
q.append(pop_node.right)
#找到給定data的父節點
def get_parent(self,data):
#如果給定data爲root的data,返回None
if self.root.data == data:
return None
else:
#依次輪詢每一個節點的左右子節點,若這個節點的左右子節點的數據爲給定的data,則返回這個節點
#否則依次添加其左右子節點進temp列表,不斷輪詢查找,若輪詢完所有的節點依舊沒有找到符合的節點,返回None
temp = [self.root]
while temp:
pop_node = temp.pop(0)
if pop_node.right is not None and pop_node.right.data == data:
return pop_node
if pop_node.left is not None and pop_node.left.data == data:
return pop_node
if pop_node.left is not None:
temp.append(pop_node.left)
if pop_node.right is not None:
temp.append(pop_node.right)
return None
#刪除給定data的節點
"""
刪除節點的情況有以下幾種:
1、刪除的節點爲root,刪除失敗;
2、刪除的節點無子節點,直接刪除
3、刪除的節點只有一個子節點,其子節點接替刪除節點的位置
4、刪除的節點有2個子節點。這裏面又要判斷刪除的節點的右子節點有無左子節點:
> 如無,將右子節點替換刪除的節點即可;如有,找到最左子節點,去替換要刪除的節點,
> 還要注意最左子節點可能也有右子節點,這個節點也要接到最左子節點的父節點上去
"""
def delete(self,data):
if self.root is None:
return False
#找出給定data的節點的父節點
parent = self.get_parent(data)
if parent:
#確定待刪除節點
del_node = parent.left if parent.left.data == data else parent.right
#若待刪除節點的左子樹爲空
if del_node.left is None:
#如果待刪除節點是其父節點的左孩子
if parent.left.data == data:
parent.left = del_node.right
else:
parent.right = del_node.right
del del_node
return True
#若待刪除節點的右子樹爲空
elif del_node.right is None:
# 如果待刪除節點是父節點的左孩子
if parent.left.data == data:
parent.left = del_node.left
# 如果待刪除節點是父節點的右孩子
else:
parent.right = del_node.left
# 刪除變量 del_node
del del_node
return True
#當左右子樹都不爲空
else:
tmp_pre = del_node
#待刪除節點的右子樹
tmp_next = del_node.right
#尋找待刪除節點右子樹中的最左葉子節點並完成替代
#如果待刪除節點的右子樹的左子樹爲空
if tmp_next.left is None:
tmp_pre.right = tmp_next.right
tmp_next.left = del_node.left
tmp_next.right = del_node.right
else:
#while循環把tmp_next指向右子樹的最左葉子節點
while tmp_next.left:
tmp_pre = tmp_next
tmp_next = tmp_next.left
tmp_pre.left = tmp_next.right
tmp_next.left = del_node.left
tmp_next.right = del_node.right
if parent.left.data == data:
parent.left == tmp_next
else:
parent.right = tmp_next
del del_node
return True
else:
return False
#二叉樹的中序遍歷
def inorder(self,node):
if node is None:
return []
result = [node.data]
left_data = self.inorder(node.left)
right_data = self.inorder(node.right)
return left_data + result + right_data
#二叉樹的先序遍歷
def preorder(self,node):
if node is None:
return []
result = [node.data]
left_data = self.preorder(node.left)
right_data = self.preorder(node.right)
return result + left_data + right_data
#二叉樹的後序遍歷
def postorder(self,node):
if node is None:
return []
result = [node.data]
left_data = self.preorder(node.left)
right_data = self.preorder(node.right)
return left_data + right_data + result
#作業:初始化創建 Node,Tree類;將1-10添加到二叉樹裏面(使用add());將三種遍歷過程打印出來
if __name__ == "__main__":
t = Tree()
for i in range(1,11):
t.add(i)
print("中序遍歷",t.inorder(t.root))
print("先序遍歷",t.preorder(t.root))
print("後序遍歷",t.postorder(t.root))
字典樹
字典樹介紹可見:看動畫輕鬆理解「Trie樹」
python實現字典樹
class TrieNode:
#初始化字典樹節點,每個節點都是字典,其鍵爲某字符,其值爲對應的子節點
def __init__(self):
self.nodes = dict()
#是否葉子節點
self.is_leaf = False
#插入單詞
def insert(self,word:str):
curr = self
for char in word:
if char not in curr.nodes:
curr.nodes[char] = TrieNode()
curr = curr.nodes[char]
curr.is_leaf = True
#插入多個單詞
def insert_many(self,words:[str]):
for word in words:
self.insert(word)
#查詢單詞是否在字典樹中
def search(self,word:str):
curr = self
for char in word:
if char not in curr.nodes:
return False
curr = curr.nodes[char]
return curr.is_leaf
堆
堆有兩點需要了解
- 堆一般採用完全二叉樹;
- 堆中的每一個節點都大於其左右子節點(大頂堆),或者堆中每一個節點都小於其左右子節點(小頂堆)。
python實現堆
import math
class Heap:
def __init__(self):
#初始化一個空堆,使用數組來初始化堆元素
self.data_list = []
#得到指定序號的節點的父節點的序號
def get_parent_index(self,index):
if index == 0 or index > len(self.data_list) - 1:
return None
else:
return (index-1) >> 1
#交換兩個序號的數據
def swap(self,index_a,index_b):
self.data_list[index_a],self.data_list[index_b] = self.data_list[index_b],self.data_list[index_a]
#插入元素
def insert(self,data):
# 先把元素放在最後,然後從後往前依次堆化
# 這裏以大頂堆爲例,如果插入元素比父節點大,則交換,直到最後
self.data_list.append(data)
index = len(self.data_list)-1
parent = self.get_parent_index(index)
while parent is not None and data > self.data_list[parent]:
#交換操作
self.swap(index,parent)
index = parent
parent = self.get_parent_index(parent)
#刪除堆頂元素
def removeMax(self):
remove_data = self.data_list[0]
self.data_list[0] = self.data_list[-1]
del self.data_list[-1]
#堆化
self.heapify(0)
return remove_data
def heapify(self,index):
#從上往下堆化
total_index = len(self.data_list)
while True:
maxvalue_index = index
if 2*index+1 < total_index and self.data_list[2*index+1] > self.data_list[maxvalue_index]:
maxvalue_index = 2*index+1
if 2*index+2 < total_index and self.data_list[2*index+2] > self.data_list[maxvalue_index]:
maxvalue_index = 2*index+2
if maxvalue_index == index:
break
self.swap(index,maxvalue_index)
index = maxvalue_index
if __name__ == "__main__":
myheap = Heap()
for i in range(10):
myheap.insert(i+1)
print("建堆:",myheap.data_list)
print("刪除堆頂元素:",[myheap.removeMax() for i in range(10)])
圖
圖的鄰接表實現
圖的點集可以用列表實現,圖的邊集可以用字典實現
python以鄰接表實現圖(無向圖)
#無向圖的鄰接表表現形式
class Graph(object):
def __init__(self):
self.nodes = []
self.edge = {}
def insert(self,a,b):
#如果a不在圖的點集裏,則添加a
if not(a in self.nodes):
self.nodes.append(a)
self.edge[a] = list()
if not(b in self.nodes):
self.nodes.append(b)
self.edge[b] = list()
#a連接b
self.edge[a].append(b)
#b連接a
self.edge[b].append(a)
#返回與a連接的點
def succ(self,a):
return self.edge[a]
#返回圖的點集
def show_nodes(self):
return self.nodes
def show_edge(self):
print(self.edge)
if __name__ == "__main__":
graph = Graph()
graph.insert('0', '1')
graph.insert('0', '2')
graph.insert('0', '3')
graph.insert('1', '3')
graph.insert('2', '3')
graph.show_edge()
圖的鄰接矩陣實現
python以鄰接矩陣實現圖(無向圖)
class Graph:
def __init__(self,vertex):
#圖有幾個節點
self.vertex = vertex
#初始化圖的鄰接矩陣,全爲0
self.graph = [[0] * vertex for i in range(vertex)]
def insert(self,u,v):
#對存在連接關係的兩個點,在矩陣裏置1代表存在連接關係,沒有連接關係則置0
self.graph[u-1][v-1] = 1
self.graph[v-1][u-1] = 1
#顯示圖的鄰接矩陣
def show(self):
for i in self.graph:
for j in i:
print(j,end = " ")
print(" ")
if __name__ == "__main__":
graph = Graph(5)
graph.insert(1,4)
graph.insert(4,2)
graph.insert(4,5)
graph.insert(2,5)
graph.insert(5,3)
graph.show()
圖的深度優先遍歷
圖的深度優先遍歷算法的主要思想是:
-
首先以一個未被訪問過的頂點作爲起始頂點,沿當前頂點的邊選擇一條支路一直往下探尋未訪問過的頂點;
-
當支路一頭扎到底,沒有未訪問過的頂點時,則回退上一個頂點,繼續往下探尋,簡而言之就是一頭扎到底,走到盡頭時就回退,然後再繼續一頭扎到底,循環往復;
深度優先遍歷算法簡而言之就是一頭扎到底,走到盡頭時就回退,然後再繼續一頭扎到底,循環往復。
python實現圖的深度優先遍歷算法
#圖的深度優先遍歷算法實現
def dfs(G,s,S=None,res=None):
if S is None:
# 儲存已經訪問節點
S=set()
if res is None:
# 存儲遍歷順序
res=[]
res.append(s)
S.add(s)
for u in G[s]:
if u in S:
continue
S.add(u)
dfs(G,u,S,res)
return res
G = {'0': ['1', '2'],
'1': ['2', '3'],
'2': ['3', '5'],
'3': ['4'],
'4': [],
'5': []}
print(dfs(G, '0'))
圖的廣度優先遍歷
廣度優先搜索(BFS)是樹的按層次遍歷的推廣,它的基本思想是:首先訪問初始點 vi,並將其標記爲已訪問過,接着訪問 vi 的所有未被訪問過的鄰接點 vi1,vi2,…, vin,並均標記已訪問過,然後再按照 vi1,vi2,…, vin 的次序,訪問每一個頂點的所有未被訪問過的鄰接點,並均標記爲已訪問過,依次類推,直到圖中所有和初始點 vi 有路徑相通的頂點都被訪問過爲止。
python實現圖的廣度優先遍歷算法
#圖的廣度優先遍歷算法實現
def bfs(graph,start):
# explored:已經遍歷的節點列表,queue:尋找待遍歷的節點隊列
explored = []
queue = [start]
explored.append(start)
while queue:
#將要遍歷v節點
v = queue.pop(0)
for w in graph[v]:
if w not in explored:
explored.append(w)
queue.append(w)
return explored
G = {'0': ['1', '2'],
'1': ['2', '3'],
'2': ['3', '5'],
'3': ['4'],
'4': [],
'5': []}
print(bfs(G,"0"))
並查集
python實現並查集
#用python實現一個並查集,思路見:https://segmentfault.com/a/1190000013805875
class Union_find:
#並查集:類似於森林
def __init__(self,data_list):
self.father_dict = {}
self.set_size = {}
for node in data_list:
self.father_dict[node] = node
self.set_size[node] = 1
#尋找父節點(最終父節點)
def find(self,node):
father = self.father_dict[node]
if node != father:
father = self.find(father)
self.father_dict[node] = father
return father
#判斷兩個節點是不是同一個集合裏的,只需判斷兩個節點的父節點是不是同一個
def is_same_set(self,node_a,node_b):
return self.find(node_a) == self.find(node_b)
#將兩個集合合併在一起
def union(self,node_a,node_b):
if node_a is None or node_b is None:
return
a_head = self.find(node_a)
b_head = self.find(node_b)
if a_head != b_head:
a_set_size = self.set_size[a_head]
b_set_size = self.set_size[b_head]
#將小集合合併在大的集合裏
if a_set_size >= b_set_size:
self.father_dict[b_head] = a_head
self.set_size[a_head] = a_set_size + b_set_size
else:
self.father_dict[a_head] = b_set_size
self.set_size[b_head] = a_set_size + b_set_size
if __name__ == "__main__":
a = [1,2,3,4,5]
union_a = Union_find(a)
union_a.union(1,2)
union_a.union(3,5)
union_a.union(3,1)
print(union_a.is_same_set(2,5))