1. 程式人生 > 其它 >牛客網高頻演算法題系列-BM7-連結串列中環的入口結點

牛客網高頻演算法題系列-BM7-連結串列中環的入口結點

課程小結

定義

(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 (克魯斯卡爾) 演算法求出。

原理

  1. 環屬性:一棵生成樹上,增加一條邊E,再刪除E所在環上的最大邊,會得到一個“更好”的生成樹 (如果E不是最大邊)
  2. 剪下屬性:在圖中,剪下將頂點劃分成兩個不相交集合。交叉邊為地些頂點在兩個不同集合的邊。對於任何一個剪下,各條最小的交叉邊都屬於某個MST,且每個MST中都包含一條最小交叉邊。
  3. 最小邊原則:圖中權值最小的邊(如果唯一的話)一定在最小生成樹上。
  4. 唯一性:一棵生成樹上,如果各邊的權都不相同,則最小生成樹是唯一的。反之不然。

Prim演算法

  1. 輸入:一個加權連通圖,一個加權連通圖,其中頂點集合為V,邊集合為E;

  2. 初始化:Include = {StartId}。

  3. 重複下列操作,直至Include = V:

    1. 在集合E中選取權值最小的邊<V1, V2>,其中V1為集合Include中的元素,而V2不在Include集合當中;
    2. 把V2加入Include中。
  4. 輸出:使用集合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演算法

  1. 先構造一個只有N個頂點,而邊集為空的子圖,若將子圖的各個頂點看成是各棵樹上的根節點,則它是一個含有N棵樹的一個森林。
  2. 從邊集 E 中選取一條權值最小的邊,若該條邊的兩個頂點分屬不同的樹,則將其加入子圖,也就是說,將這兩個頂點分別所在的兩棵樹合成一棵樹;
  3. 反之,若該條邊的兩個頂點已落在同一棵樹上,則不可取,而應該取下一條權值最小的邊再試之。依次類推,直至森林中只有一棵樹,也即子圖中含有 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;
}