1. 程式人生 > >【演算法——Python實現】有權圖求最小生成樹Prim演算法

【演算法——Python實現】有權圖求最小生成樹Prim演算法

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