最小全域木
接続された無向グラフG =(V、E)G =(V、E)と仮定しますG=(V 、E )、ここで、各エッジ(u、v)∈E(u、v)\ in E(u 、v )∈E、重みw(u、v)w(u、v)を与えますw (u 、v )、非巡回サブセットT⊆ET\ subseteqEを見つけたいT⊆Eは、すべてのノードを接続できますが、重みも最小です。つまり、w(T)= ∑(u、v)∈Tw(u。V)w(T)= \ sum _((u、v)\ in T)w(uv)w (T )=∑(U 、V )∈ T値W (U 。vが)最も小さいです。TTのおかげでTは非周期的であり、すべてのノードを接続します。したがって、TTTは木でなければなりません。このようなツリーをスパニングツリーと呼び、スパニングツリーを取得する問題を最小スパニングツリー問題と呼びます。次の図は、接続されたグラフとその最小スパニングツリーの例を示しています。グラフでは、最小スパニングツリーに
属するエッジに陰影が付けられ、図に示されているスパニングツリーの合計の重みは37です。スパニングツリーが一意ではない場合、エッジを削除します(b、c)(b、c)(b 、c )、次にエッジ(a、h)(a、h)を追加します(、h )、37の重みを持つ別の最小全域木を形成します。
最小スパニングツリーの問題を解決するには、クラスカルアルゴリズムとプリムアルゴリズムの2つのアルゴリズムがあります。両方の最小スパニングツリーアルゴリズムは貪欲なアルゴリズムです。
クラスカルアルゴリズムとプリムアルゴリズム
クラスカルアルゴリズムでは、最小全域木問題の2つの古典的なアルゴリズムでAAを設定します。Aはフォレストであり、そのノードは、セットAAに追加されるたびに、特定のグラフのノードになります。Aの安全なエッジは、常に最小の重みを持つ2つの異なるコンポーネントを接続するエッジです。プリムアルゴリズムで、AAを設定します。Aはツリーであり、AAに追加されるたびにAの安全側は常にAAに接続されていますA和 A A A以外のノードのエッジの中で重みが最小のエッジ。
クラスカルアルゴリズム
クラスカルアルゴリズムは、フォレスト内の2つの異なるツリーを接続するすべてのエッジの中で、重み(u、v)(u、v)が最小のエッジを見つけることによって安全なエッジを見つけます。(u 、v )、Kruskalアルゴリズムは貪欲アルゴリズムです。これは、重みが最小のエッジを選択して、毎回フォレストに参加するためです。次の図は、クラスカルのアルゴリズムの作業プロセスを示しています。
影付きのエッジは、成長するフォレストAAに属しています。A、アルゴリズムは次にエッジ、矢印によって指さエッジの重みを考慮したエッジは、アルゴリズムの各ステップで検討されています。エッジが2つの異なるツリーを接続している場合、それはフォレストに追加され、2つのツリーのマージが完了します。
実現のアイデア
入力:グラフ
出力:最小ツリー
最小ツリーは次の条件を満たす:
- すべてのノードが含まれます
- グラフで形成されたすべてのツリーの中で、合計スコアが最も小さいツリー
実装手順
- すべてのエッジを重みに従って小さいものから大きいものに並べ替えます
- 最小のエッジが選択されてツリーに追加されるたびに、新しく追加されたエッジによってツリーにリングが発生した場合、そのエッジは破棄されます。
- ツリーにすべてのノードが含まれるまで、上記の2でエッジを追加する操作を繰り返します。
Pythonの実装コードは次のとおりです。
# -*-coding:utf8 -*-
import sys
class Graph:
def __init__(self, vertices):
self.V = vertices
self.graph = []
def add_edge(self, u, v, w):
self.graph.append([u,v,w])
# 找到节点所在的树的根节点
def find(self, parent, i):
if parent[i] == i:
return i
return self.find(parent, parent[i])
# 合并新的节点到一棵树中来
def apply_union(self, parent, rank, x, y):
xroot = self.find(parent, x)
yroot = self.find(parent, y)
if rank[xroot] < rank[yroot]:
parent[xroot] = yroot
elif rank[xroot] > rank[yroot]:
parent[yroot] = xroot
else:
parent[yroot] = xroot
rank[xroot] +=1
def kruskal(self):
result = []
i, e = 0, 0
#排序,边按照权重从小到大排序
self.graph = sorted(self.graph, key=lambda item: item[2])
parent = []
rank = []
#初始,每个节点构成一棵树,根节点就是自己
for node in range(self.V):
parent.append(node)
rank.append(0)
# 做V-1个节点选择
while e < self.V - 1:
u, v, w = self.graph[i]
i = i+1
x = self.find(parent, u)
y = self.find(parent, v)
# 选择的边的两个节点不在同一棵树,则合并
if x != y:
e = e + 1
result.append([u, v, w])
self.apply_union(parent, rank, x, y)
#打印每一次选择的边
for u, v , weight in result:
print("%d - %d: %d" % (u, v, weight))
if __name__=='__main__':
g = Graph(6)
for x,y,w in [(0,1,4),(0,2,4),(1,2,2),(1,0,4),(2,0,4),(2,1,2),(2,3,3),(2,5,2),(2,4,4),(3,2,3),(3,4,3),(4,2,4),(4,3,3),(5,2,2),(5,4,3)]:
g.add_edge(x,y,w)
g.kruskal()
クラスカルのアルゴリズムの時間計算量はO(E lg E)O(ElgE)です。O (E l g E )
プリムアルゴリズム
プリムのアルゴリズムの1つのプロパティはAAに設定されていますサイドツリーを構成常に、ルートノードからの任意のツリーは、カバーに育てられたVVVのすべてのノードはこれまでのところです。この戦略も貪欲な戦略です。各ステップで追加されるエッジは、ツリーの総重量の増加を最小限に抑えるエッジでなければならないためです。
次の図に示すように、プリムアルゴリズムを実行するプロセス。最初のノードはaaです。a、影付きのエッジと黒いノードはツリーAAに属しますA。
実装手順
- 最小ツリーを初期化するノードをランダムに選択します
- ツリーと新しいノードを接続するすべてのエッジについて、重みが最小のエッジを選択します
- すべてのノードが含まれるまで、上記の2を繰り返します
Pythonは次のように実装されています。
# -*-coding:utf8 -*-
import sys
INF = 9999999
class Graph:
def __init__(self, V, G):
self.V = V
self.G = G
def prim(self):
selected = [0] * self.V
no_edge = 0
selected[0] = True
print("Edge : Weight")
#需要选择V-1个
while (no_edge < self.V - 1):
minimum = INF
x = 0
y = 0
# 遍历V个节点
for i in range(V):
#该节点选择了的
if selected[i]:
for j in range(self.V):
#选择的邻接节点没有选择的,且有边的
if ((not selected[j]) and self.G[i][j]):
if minimum > self.G[i][j]:
minimum = self.G[i][j]
x = i
y = j
print(str(x) + "-" + str(y) + ":" + str(self.G[x][y]))
selected[y] = True
no_edge += 1
if __name__=='__main__':
V = 5
G = [[0, 9, 75, 0, 0],
[9, 0, 95, 19, 42],
[75, 95, 0, 51, 66],
[0, 19, 51, 0, 31],
[0, 42, 66, 31, 0]]
graph = Graph(V,G)
graph.prim()
プリムのアルゴリズムの複雑さはO(E log V)O(ElogV)です。O (E l o g V )