【演算法——Python實現】有權圖求最小生成樹Prim演算法
阿新 • • 發佈:2019-02-07
class Edge(object):
"""邊"""
def __init__(self, a, b, weight):
self.a = a # 第一個頂點
self.b = b # 第二個頂點
self.weight = weight # 權值
def v(self):
return self.a
def w(self):
return self.b
def wt(self):
return self.weight
def other(self, x) :
# 返回x頂點連線的另一個頂點
if x == self.a or x == self.b:
if x == self.a:
return self.b
else:
return self.a
def __lt__(self, other):
# 小於號過載
return self.weight < other.wt()
def __le__(self, other):
# 小於等於號過載
return self.weight <= other.wt()
def __gt__(self, other):
# 大於號過載
return self.weight > other.wt()
def __ge__(self, other):
# 大於等於號過載
return self.weight >= other.wt()
def __eq__(self, other):
# ==號過載
return self.weight == other.wt()
class DenseGraph(object):
"""有權稠密圖 - 鄰接矩陣"""
def __init__(self, n, directed):
self.n = n # 圖中的點數
self.m = 0 # 圖中的邊數
self.directed = directed # bool值,表示是否為有向圖
self.g = [[None for _ in range(n)] for _ in range(n)] # 矩陣初始化都為None的二維矩陣
def V(self):
# 返回圖中點數
return self.n
def E(self):
# 返回圖中邊數
return self.m
def addEdge(self, v, w, weight):
# v和w中增加一條邊,v和w都是[0,n-1]區間
if v >= 0 and v < n and w >= 0 and w < n:
if self.hasEdge(v, w):
self.m -= 1
self.g[v][w] = Edge(v, w, weight)
if not self.directed:
self.g[w][v] = Edge(w, v, weight)
self.m += 1
def hasEdge(self, v, w):
# v和w之間是否有邊,v和w都是[0,n-1]區間
if v >= 0 and v < n and w >= 0 and w < n:
return self.g[v][w] != None
class adjIterator(object):
"""相鄰節點迭代器"""
def __init__(self, graph, v):
self.G = graph # 需要遍歷的圖
self.v = v # 遍歷v節點相鄰的邊
self.index = 0 # 遍歷的索引
def __iter__(self):
return self
def next(self):
while self.index < self.G.V():
# 當索引小於節點數量時遍歷,否則為遍歷完成,停止迭代
if self.G.g[self.v][self.index]:
r = self.G.g[self.v][self.index]
self.index += 1
return r
self.index += 1
raise StopIteration()
class SparseGraph(object):
"""有權稀疏圖- 鄰接表"""
def __init__(self, n, directed):
self.n = n # 圖中的點數
self.m = 0 # 圖中的邊數
self.directed = directed # bool值,表示是否為有向圖
self.g = [[] for _ in range(n)] # 矩陣初始化都為空的二維矩陣
def V(self):
# 返回圖中點數
return self.n
def E(self):
# 返回圖中邊數
return self.m
def addEdge(self, v, w, weight):
# v和w中增加一條邊,v和w都是[0,n-1]區間
if v >= 0 and v < n and w >= 0 and w < n:
# 考慮到平行邊會讓時間複雜度變為最差為O(n)
# if self.hasEdge(v, w):
# return None
self.g[v].append(Edge(v, w, weight))
if v != w and not self.directed:
self.g[w].append(Edge(w, v, weight))
self.m += 1
def hasEdge(self, v, w):
# v和w之間是否有邊,v和w都是[0,n-1]區間
# 時間複雜度最差為O(n)
if v >= 0 and v < n and w >= 0 and w < n:
for i in self.g[v]:
if i.other(v) == w:
return True
return False
class adjIterator(object):
"""相鄰節點迭代器"""
def __init__(self, graph, v):
self.G = graph # 需要遍歷的圖
self.v = v # 遍歷v節點相鄰的邊
self.index = 0 # 遍歷的索引
def __iter__(self):
return self
def next(self):
if len(self.G.g[self.v]):
# v有相鄰節點才遍歷
if self.index < len(self.G.g[self.v]):
r = self.G.g[self.v][self.index]
self.index += 1
return r
else:
raise StopIteration()
else:
raise StopIteration()
class ReadGraph(object):
"""讀取檔案中的圖"""
def __init__(self, graph, filename):
with open(filename, 'r') as f:
line = f.readline()
line = line.strip('\n')
line = line.split()
v = int(line[0])
e = int(line[1])
if v == graph.V():
lines = f.readlines()
for i in lines:
a, b, w = self.stringstream(i)
if a >= 0 and a < v and b >=0 and b < v:
graph.addEdge(a, b, w)
def stringstream(self, text):
result = text.strip('\n')
result = result.split()
a, b, w = result
return int(a), int(b), float(w)
class IndexMinHeap(object):
"""
最小反向索引堆,出堆不刪除data,只刪除indexs
"""
def __init__(self, n):
self.capacity = n # 堆的最大容量
self.data = [-1 for _ in range(n)] # 建立堆
self.indexs = [] # 建立索引堆
self.reverse = [-1 for _ in range(n)] # 建立反向索引
self.count = 0 # 元素數量
def size(self):
return self.count
def isEmpty(self):
return self.count == 0
def insert(self, i, item):
# 插入元素入堆
self.data[i] = item
self.indexs.append(i)
self.reverse[i] = self.count
self.count += 1
self.shiftup(self.count)
def shiftup(self, count):
# 將插入的元素放到合適位置,保持最小堆
while count > 1 and self.data[self.indexs[(count/2)-1]] > self.data[self.indexs[count-1]]:
self.indexs[(count/2)-1], self.indexs[count-1] = self.indexs[count-1], self.indexs[(count/2)-1]
self.reverse[self.indexs[(count/2)-1]] = (count/2)-1
self.reverse[self.indexs[count-1]] = count-1
count /= 2
def extractMin(self):
# 出堆
if self.count > 0:
ret = self.data[self.indexs[0]]
self.indexs[0], self.indexs[self.count-1] = self.indexs[self.count-1], self.indexs[0]
self.reverse[self.indexs[0]] = 0
self.reverse[self.indexs[self.count-1]] = self.count-1
# for i in range(self.indexs[self.count-1]+1, self.count):
# self.indexs[self.reverse[i]] -= 1
self.reverse[self.indexs[self.count-1]] = -1
# self.data[self.indexs[self.count-1]] = -1
self.indexs.pop(self.count-1)
self.count -= 1
self.shiftDown(1)
return ret
def extractMinIndex(self):
# 出堆返回索引
if self.count > 0:
ret = self.indexs[0]
self.indexs[0], self.indexs[self.count-1] = self.indexs[self.count-1], self.indexs[0]
self.reverse[self.indexs[0]] = 0
self.reverse[self.indexs[self.count-1]] = self.count-1
# for i in range(self.indexs[self.count-1]+1, self.count):
# self.indexs[self.reverse[i]] -= 1
self.reverse[self.indexs[self.count-1]] = -1
# self.data[self.indexs[self.count-1]] = -1
self.indexs.pop(self.count-1)
self.count -= 1
self.shiftDown(1)
return ret
def shiftDown(self, count):
# 將堆的索引位置元素向下移動到合適位置,保持最小堆
while 2 * count <= self.count :
# 證明有孩子
j = 2 * count
if j + 1 <= self.count:
# 證明有右孩子
if self.data[self.indexs[j]] < self.data[self.indexs[j-1]]:
# 右孩子數值比左孩子數值小
j += 1
if self.data[self.indexs[count-1]] <= self.data[self.indexs[j-1]]:
# 堆的索引位置已經小於兩個孩子節點,不需要交換了
break
self.indexs[count-1], self.indexs[j-1] = self.indexs[j-1], self.indexs[count-1]
self.reverse[self.indexs[count-1]] = count-1
self.reverse[self.indexs[j-1]] =j-1
count = j
def getItem(self, i):
# 根據索引獲取數值
if i >=0 and i <= self.count-1:
return self.data[i]
else:
return None
def change(self, i, newItem):
# 改變i索引位置的數值
if i >=0:
self.data[i] = newItem
j = self.reverse[i]
self.shiftup(j+1)
self.shiftDown(j+1)
class PrimMST(object):
"""最小生成樹"""
def __init__(self, graph):
self.G = graph # 傳入圖
self.ipq = IndexMinHeap(self.G.V()) # 最小索引堆,存權值
self.edgeTo = [None for _ in range(self.G.V())] # 存Edge邊,表示存放連線索引節點的邊
self.marked = [False for _ in range(self.G.V())] # 用於標記已經被選取為樹的節點,初始都為False
self.mst = [] # 記錄被選取的邊
self.mstWeight = 0 # 記錄最小生成樹的總權值
# Prim
self.visit(0)
while not self.ipq.isEmpty():
# 取出權值最小的橫切邊的索引,該索引表示這條邊與哪個節點相連線
v = self.ipq.extractMinIndex()
self.mst.append(self.edgeTo[v])
# 即將將節點標記為已訪問(紅色)
self.visit(v)
self.mstWeight = sum([i.wt() for i in self.mst])
def visit(self, v):
# 訪問
if not self.marked[v]:
# self.marked[v]為False,表示該節點還未被加入樹中
self.marked[v] = True
adj = self.G.adjIterator(self.G, v)
for i in adj:
# 與v相連的另一端
w = i.other(v)
if not self.marked[w]:
# 與v相連線的另一端還未被加入樹中,則這一條邊為橫切邊
if not self.edgeTo[w]:
# 如果連線w端點的邊還沒有橫切邊,則直接把權值插入到最小索引堆
self.ipq.insert(w, i.wt())
self.edgeTo[w] = i
elif i.wt() < self.edgeTo[w].wt():
# 如果有連線w端點的橫切邊,且新的橫切邊的權值小於原有橫切邊的權值,則用新的橫切邊替代
self.edgeTo[w] = i
self.ipq.change(w, i.wt())
def mstEdges(self):
# 查詢最小生成樹的邊
return self.mst
def result(self):
# 返回最小生成樹的權值
return self.mstWeight