牛客網高頻演算法題系列-BM7-連結串列中環的入口結點
阿新 • • 發佈:2022-05-31
課程小結
定義
(1) 定義
生成樹:
樹:N個點,N - 1條邊的連通圖
生成樹:包含某圖G所有點的樹
一個圖G是樹當且僅當以下任意一個條件成立
G有V-1條邊,無環
G有V-1條邊,連通
任意兩點只有唯一的簡單路徑
G連通,但刪除任意一條邊後不連通
最小生成樹:
一個有N個結點的連通圖是原圖的極小連通子圖,且包含原圖中的所有N個節點,並且有保持圖連通的最少的邊。
在一給定的無向圖G = (V, E)中,(U, V) 代表連線U和V的邊, 而w(U, V)代表此邊的權重,若存在T為E的子集且為無環圖,使得聯通所有節點的w(T)最小,則此T為G的最小生成樹。
生成樹:一個|V|個點的的圖,取其中|V| - 1條邊,並連線所有的頂點,則組成原圖的一個生成樹 。
屬性:|V| - 1條邊、連通、無環。
最小生成樹:加權圖的最小生成樹是一棵生成樹,其所有邊的權值之和不會大於其它任何生成樹。
簡單講:找到連線所有點的最低成本路線。
最小生成樹可以用Prim (普里姆) 演算法或 Kruskal (克魯斯卡爾) 演算法求出。
原理
- 環屬性:一棵生成樹上,增加一條邊E,再刪除E所在環上的最大邊,會得到一個“更好”的生成樹 (如果E不是最大邊)
- 剪下屬性:在圖中,剪下將頂點劃分成兩個不相交集合。交叉邊為地些頂點在兩個不同集合的邊。對於任何一個剪下,各條最小的交叉邊都屬於某個MST,且每個MST中都包含一條最小交叉邊。
- 最小邊原則:圖中權值最小的邊(如果唯一的話)一定在最小生成樹上。
- 唯一性:一棵生成樹上,如果各邊的權都不相同,則最小生成樹是唯一的。反之不然。
Prim演算法
-
輸入:一個加權連通圖,一個加權連通圖,其中頂點集合為V,邊集合為E;
-
初始化:Include = {StartId}。
-
重複下列操作,直至Include = V:
- 在集合E中選取權值最小的邊<V1, V2>,其中V1為集合Include中的元素,而V2不在Include集合當中;
- 把V2加入Include中。
-
輸出:使用集合Include來描述得到的最小生成樹。
MST_Prim(G, r) //從任意點r出發,生長成一MST for i=1 to n do dis[i] <- ∞ // 初始化每點到Vnew集合的最小值 Vnew[i] <- false //設頂點不在Vnew中 dis[r] <- 0 //將r設為0(或- ∞ ),準備取出 for i=1 to n do v <- get-min() //取dis[?]中最小的值c和頂點v, Vnew[ v ] <- true //v放入Vnew中 sum <- sum+c //c加入MST的總和中 updata( v ) //列舉交叉邊(v,B),改進dis[ ]
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
#define Inf 0x7f7f7f7f
#define MemInf 0x7f
int Node, Edge, Node1, Node2, Weight;
int Graph[105][105];
bool Include[105];
int Dis[105];
struct Get_Min_Return_Value_Node {
int Node;
int Weight;
};
void Update(int NodeId) {
for (int i = 1; i <= Node; i++) {
if (Include[i] == false && Graph[NodeId][i] != Inf) {
Dis[i] = min(Dis[i], Graph[NodeId][i]);
}
}
}
Get_Min_Return_Value_Node Get_Min( ) {
Get_Min_Return_Value_Node Result = { 0,0x7f7f7f7f };
for (int i = 1; i <= Node; i++) {
if (Include[i] == false && Dis[i] < Result.Weight) {
Result.Node = i;
Result.Weight = Dis[i];
}
}
return Result;
}
int Make_MST(int StartId) {
memset(Dis, MemInf, sizeof(Dis));
memset(Include, false, sizeof(Include));
Dis[StartId] = 0;
int Result = 0;
for (int i = 1; i <= Node; i++) {
Get_Min_Return_Value_Node Current_Result = Get_Min( );
Include[Current_Result.Node] = true;
Result = Result + Current_Result.Weight;
Update(Current_Result.Node);
}
return Result;
}
int Prim( ) {
int Result = Inf;
for (int i = 1; i <= Node; i++) {
Result = min(Result, Make_MST(i));
}
return Result;
}
int main( ) {
cin >> Node >> Edge;
memset(Graph, MemInf, sizeof(Graph));
int Sum = 0;
for (int i = 1; i <= Edge; i++) {
cin >> Node1 >> Node2 >> Weight;
Graph[Node1][Node2] = Graph[Node2][Node1] = Weight;
Sum = Sum + Weight;
}
cout << Sum - Prim( ) << '\n';
return 0;
}
Kruskal演算法
- 先構造一個只有N個頂點,而邊集為空的子圖,若將子圖的各個頂點看成是各棵樹上的根節點,則它是一個含有N棵樹的一個森林。
- 從邊集 E 中選取一條權值最小的邊,若該條邊的兩個頂點分屬不同的樹,則將其加入子圖,也就是說,將這兩個頂點分別所在的兩棵樹合成一棵樹;
- 反之,若該條邊的兩個頂點已落在同一棵樹上,則不可取,而應該取下一條權值最小的邊再試之。依次類推,直至森林中只有一棵樹,也即子圖中含有 n-1條邊為止。
演算法描述 :
MST_Kruskal(G)
(1)將G所有條邊按權從小到大排序;圖MST開始為空
(2)從小到大次序取邊(V1,V2)
(3)若加入邊(V1,V2),MST就有環,則放棄此邊,轉(2)
(4)將邊(V1,V2)加入MST,如果已經加了N - 1條邊,結束。否則,轉 (2)
虛擬碼
MST_Kruskal(G)
for i=1 to n do f[i] <- i; //初始化並查集
sort( e, e+m); //邊按大小排序
c <- 0; //取邊的計數器
for i=1 to m do //從小到大取邊
v <- find_set( e[i].v ); //左端點所在連通塊“根”
u <- find_set( e[i].u ); //右端點所在連通塊“根”
if(v != u) //如果不在同一連通塊
union(v,u); //合併兩連通塊
sum += g[v][u]; //加入這條邊的權
if (++c == n-1) break; //if 取了n-1條邊,結束
程式碼
//標頭檔案
#include <iostream>
#include <algorithm>
//簡化程式碼
using namespace std;
//常量
const int Inf = 0x3f3f3f3f;
//變數、陣列
struct Edge_Node {
int Node1, Node2, Weight;
};
int Node, Edge;
Edge_Node Edges[200005];
//排序函式
bool Compare_Function(Edge_Node Edge_A, Edge_Node Edge_B) {
return Edge_A.Weight < Edge_B.Weight;
}
//並查集
int UFS[20005];
void Init( ) {
for (int i = 1; i <= Node; i++) UFS[i] = i;
}
int Find(int Id) {
int Result = Id;
for (; UFS[Result] != Result; Result = UFS[Result]);
for (int i = Id; UFS[i] != i;) {
int Next = UFS[i];
UFS[i] = Result;
i = Next;
}
return Result;
}
void Union(int Root_A, int Root_B) {
UFS[Root_B] = Root_A;
}
//演算法
int Node_Num;
int Kruskal( ) {
Init( );
sort(Edges + 1, Edges + Edge + 1, Compare_Function);
Node_Num = 0;
int Sum = 0;
for (int i = 1; i <= Edge; i++) {
int Root_1 = Find(Edges[i].Node1), Root_2 = Find(Edges[i].Node2);
if (Root_1 != Root_2) {
Union(Root_1, Root_2);
Sum += Edges[i].Weight;
Node_Num++;
if (Node_Num == Node - 1) break;
}
}
return Sum;
}
//主函式
int main( ) {
cin >> Node >> Edge;
int Sum = 0;
for (int i = 1; i <= Edge; i++) {
cin >> Edges[i].Node1 >> Edges[i].Node2 >> Edges[i].Weight;
Sum += Edges[i].Weight;
}
int Ans = Kruskal( );
if (Node_Num != Node - 1) cout << -1;
else cout << Sum - Ans << '\n';
return 0;
}