【学习笔记】比较分别用prim和kruskal实现最小生成树和算法优化方案
kruskal:
1.思路
:設(shè)G=(V,E)是無(wú)向連通帶權(quán)圖,V={,…,n};設(shè)最小生成樹(shù)T=(V,TE),該樹(shù)的初始狀態(tài)為只有n個(gè)頂點(diǎn)而無(wú)邊的非連通圖T=(V,{}),Kruskal算法將這n個(gè)頂點(diǎn)看成是n個(gè)孤立的連通分支。它首先將所有的邊按權(quán)值從小到大排序,然后只要T中選中的邊數(shù)不到n-1,就做如下的貪心選擇:在邊集E中選取權(quán)值最小的邊(i,j),如果將邊(i,j)加入集合TE中不產(chǎn)生回路(圈),則將邊(i,j)加入邊集TE中,即用邊(i,j)將這兩個(gè)連通分支合并連接成一個(gè)連通分支;否則繼續(xù)選擇下一條最短邊。把邊(i,j)從集合E中刪去。繼續(xù)上面的貪心選擇,直到T中所有頂點(diǎn)都在同一個(gè)連通分支上為止。此時(shí),選取到的n-1條邊恰好構(gòu)成G的一棵最小生成樹(shù)T。 那么,怎樣判斷加入某條邊后圖T會(huì)不會(huì)出現(xiàn)回路呢? 該算法對(duì)于手工計(jì)算十分方便,因?yàn)橛萌庋劭梢院苋菀卓吹教暨x哪些邊能夠避免構(gòu)成回路(避圈法),但使用計(jì)算機(jī)程序來(lái)實(shí)現(xiàn)時(shí),還需要一種機(jī)制來(lái)進(jìn)行判斷。Kruskal算法用了一個(gè)非常聰明的方法,就是運(yùn)用集合避圈:如果所選擇加入的邊的起點(diǎn)和終點(diǎn)都在T的集合中,那么就可以斷定一定會(huì)形成回路(圈)。其實(shí)就是我們”:邊的兩個(gè)結(jié)點(diǎn)不能屬于同一集合。 步驟1:初始化。將圖G的邊集E中的所有邊按權(quán)值從小到大排序,邊集TE={ },把每個(gè)頂點(diǎn)都初始化為一個(gè)孤立的分支,即一個(gè)頂點(diǎn)對(duì)應(yīng)一個(gè)集合。
步驟2:在E中尋找權(quán)值最小的邊(i,j)。
步驟3:如果頂點(diǎn)i和j位于兩個(gè)不同連通分支,則將邊(i,j)加入邊集TE,并執(zhí)行合并操作,將兩個(gè)連通分支進(jìn)行合并。 步驟4:將邊(i,j)從集合E中刪去,即E=E-{(i,j)}。
步驟5:如果選取邊數(shù)小于n-1,轉(zhuǎn)步驟2;否則,算法結(jié)束,生成最小生成樹(shù)T。
偽碼詳解
(1)數(shù)據(jù)結(jié)構(gòu) int nodeset[N];//集合號(hào)數(shù)組
struct Edge {//邊的存儲(chǔ)結(jié)構(gòu)
int u;
int v;
int w;
}e[N*N]; (2)初始化 void Init(int n){
for(int i = 1; i <= n; i++) nodeset[i] = i;//每個(gè)結(jié)點(diǎn)賦值一個(gè)集合號(hào)}
(3)對(duì)邊進(jìn)行排序 bool comp(Edge x, Edge y) { return x.w < y.w;//定義優(yōu)先級(jí),按邊值進(jìn)行升序排序}sort(e, e+m, comp);//調(diào)用系統(tǒng)排序函數(shù)
(4)合并集合 int Merge(int a, int b){ int p = nodeset[a];//p為a結(jié)點(diǎn)的集合號(hào) int q = nodeset[b]; //q為b結(jié)點(diǎn)的集合號(hào) if(p==q) return 0; //集合號(hào)相同,什么也不做,返回 for(int i=1;i<=n;i++)//檢查所有結(jié)點(diǎn),把集合號(hào)是q的全部改為p { if(nodeset[i]==q) nodeset[i] = p;//a的集合號(hào)賦值給b集合號(hào)
} return 1;}
kruskal完整代碼
#include <iostream> #include <cstdio> #include <algorithm> using namespace std; const int N = 100; int nodeset[N]; int n, m; struct Edge {int u;int v;int w; }e[N*N]; bool comp(Edge x, Edge y) {return x.w < y.w; } void Init(int n) {for(int i = 1; i <= n; i++) nodeset[i] = i; } int Merge(int a, int b) { int p = nodeset[a]; int q = nodeset[b]; if(p==q) return 0; for(int i=1;i<=n;i++)//檢查所有結(jié)點(diǎn),把集合號(hào)是q的改為p { if(nodeset[i]==q) nodeset[i] = p;//a的集合號(hào)賦值給b集合號(hào) } return 1;}int Kruskal(int n){ int ans = 0; for(int i=0;i<m;i++) if(Merge(e[i].u, e[i].v)) { ans += e[i].w; n--; if(n==1) return ans; } return 0;}int main() {cout <<"輸入結(jié)點(diǎn)數(shù)n和邊數(shù)m:"<<endl;cin >> n >> m;Init(n);cout <<"輸入結(jié)點(diǎn)數(shù)u,v和邊值w:"<<endl;for(int i=1;i<=m;i++)cin >> e[i].u>> e[i].v >>e[i].w;sort(e, e+m, comp);int ans = Kruskal(n);cout << "最小的花費(fèi)是:" << ans << endl;return 0; }1)時(shí)間復(fù)雜度:算法中,需要對(duì)邊進(jìn)行排序,若使用快速排序,執(zhí)行次數(shù)為eloge,算法的時(shí)間復(fù)雜度為O(eloge)。而合并集合需要n-1次合并,每次為O(n),合并集合的時(shí)間復(fù)雜度為O(n2)。
2)空間復(fù)雜度:算法所需要的輔助空間包含集合號(hào)數(shù)組 nodeset[n],則算法的空間復(fù)雜度是O(n)。
6.算法優(yōu)化拓展
該算法合并集合的時(shí)間復(fù)雜度為O(n2),我們可以用并查集的思想優(yōu)化,使合并集合的時(shí)間復(fù)雜度降為O(e*logn),優(yōu)化后的程序如下。
#include <iostream> #include <cstdio> #include <algorithm> using namespace std; const int N = 100; int father[N]; int n, m; struct Edge {int u;int v;int w; }e[N*N]; bool comp(Edge x, Edge y) {return x.w < y.w;//排序優(yōu)先級(jí),按邊的權(quán)值從小到大 } void Init(int n) {for(int i = 1; i <= n; i++)father[i] = i;//頂點(diǎn)所屬集合號(hào),初始化每個(gè)頂點(diǎn)一個(gè)集合號(hào) } int Find(int x) //找祖宗 {if(x != father[x])father[x] = Find(father[x]);//把當(dāng)前結(jié)點(diǎn)到其祖宗路徑上的所有結(jié)點(diǎn)的集合號(hào)改為祖宗集合號(hào)return father[x]; //返回其祖宗的集合號(hào) } int Merge(int a, int b) //兩結(jié)點(diǎn)合并集合號(hào) {int p = Find(a); //找a的集合號(hào)int q = Find(b); //找b的集合號(hào)if(p==q) return 0;if(p > q)father[p] = q;//小的集合號(hào)賦值給大的集合號(hào)elsefather[q] = p;return 1; } int Kruskal(int n) {int ans = 0;for(int i=0;i<m;i++) if(Merge(e[i].u, e[i].v)) { ans += e[i].w; n--; if(n==1) return ans; } return 0;}int main(){ cout <<"輸入結(jié)點(diǎn)數(shù)n和邊數(shù)m:"<<endl; cin >> n >> m; Init(n); cout <<"輸入結(jié)點(diǎn)數(shù)u,v和邊值w:"<<endl; for(int i=1;i<=m;i++) cin>>e[i].u>>e[i].v>>e[i].w; sort(e, e+m, comp); int ans = Kruskal(n); cout << "最小的花費(fèi)是:" << ans << endl; return 0;}輸入結(jié)點(diǎn)數(shù)n和邊數(shù)m:
7 12輸入結(jié)點(diǎn)數(shù)u,v和邊值w:
1 2 23
1 6 28
1 7 36
2 3 20
2 7 1
3 4 15
3 7 4
4 5 3
4 7 9
5 6 17
5 7 16
6 7 25
輸出57
prim
步驟1:確定合適的數(shù)據(jù)結(jié)構(gòu)。設(shè)置帶權(quán)鄰接矩陣C存儲(chǔ)圖G,如果圖G中存在邊(u,x),令C[u][x]等于邊(u,x)上的權(quán)值,否則,C[u][x]=∞;bool數(shù)組s[],如果s[i]=true,說(shuō)明頂點(diǎn)i已加入集合U。
可以通過(guò)設(shè)置兩個(gè)數(shù)組巧妙地解決這個(gè)問(wèn)題,closest[j]表示V-U中的頂點(diǎn)j到集合U中的最鄰近點(diǎn),lowcost[j]表示V-U中的頂點(diǎn)j到集合U中的最鄰近點(diǎn)的邊值,即邊(j,closest[j])的權(quán)值。
(https://img-blog.csdnimg.cn/2021052415592813.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxMzU4NTc0,size_16,color_FFFFFF,t_70)
例如,在圖中7號(hào)結(jié)點(diǎn)到U集合中的最鄰近點(diǎn)是2,closest[7]=2,如圖2-63所示。7號(hào)結(jié)點(diǎn)到最鄰近點(diǎn)2的邊值為1,即邊(2,7)的權(quán)值,記為lowcost[7]=1,如圖2-64所示。
只需要在V-U集合中找lowcost[ ]值最小的頂點(diǎn)即可。
步驟2:初始化。令集合U={u0},u0∈V,并初始化數(shù)組closest[]、
lowcost[]和s[]。
步驟3:在V-U集合中找lowcost值最小的頂點(diǎn)t,
即lowcost[t]=min{lowcost[j]|j∈V-U},
滿足該公式的頂點(diǎn)t就是集合V-U中連接集合U的最鄰近點(diǎn)。 步驟4:將頂點(diǎn)t加入集合U。 步驟5:如果集合V-U,算法結(jié)束,否則,轉(zhuǎn)步驟6。 步驟6:對(duì)集合V-U中的所有頂點(diǎn)j,更新其lowcost[]和closest[]。cost [j]= C [t] [j]; closest [j] = t; },轉(zhuǎn)步驟3。 按照上述步驟,最終可以得到一棵權(quán)值之和最小的生成樹(shù)。
更新公式:
if(C[t] [j]<lowcost [j] ) {
lowcost [j]= C [t] [j]; closest [j] = t; },轉(zhuǎn)步驟3。 按照上述步驟,最終可以得到一棵權(quán)值之和最小的生成樹(shù)。
偽代碼詳解
(1)初始化。s[1]=true,初始化數(shù)組closest,除了u0外其余頂點(diǎn)最鄰近點(diǎn)均為u0,表示V-U中的頂點(diǎn)到集合U的最臨近點(diǎn)均為u0;初始代數(shù)組lowcost,u0到V-U中的頂點(diǎn)的邊值,無(wú)邊相連則為∞(無(wú)窮大)
s[u0] = true; //初始時(shí),集合中U只有一個(gè)元素,即頂點(diǎn)u0
for(i = 1; i <= n; i++) {
if(i != u0) //除u0之外的頂點(diǎn) { lowcost[i] = c[u0][i]; //u0到其它頂點(diǎn)的邊值 closest[i] = u0; //最鄰近點(diǎn)初始化為u0 s[i] = false; //初始化u0之外的頂點(diǎn)不屬于U集合,即屬于V-U集合 } else lowcost[i] =0;}
2)在集合V-U中尋找距離集合U最近的頂點(diǎn)t。
int temp = INF;
int t = u0;
for(j = 1; j <= n; j++) //在集合中V-U中尋找距離集合U最近的頂點(diǎn)t
{
if((!s[j]) && (lowcost[j] < temp)) //!s[j] 表示j結(jié)點(diǎn)在V-U集合中
{
t = j;
temp = lowcost[j];
}
}
if(t == u0) //找不到t,跳出循環(huán)
break;
(3)更新lowcost和closest數(shù)組。 s[t] = true; //否則,t加入集合U
for(j = 1; j <= n; j++) //更新lowcost和closest
{
if((!s[j]) && (c[t][j] < lowcost[j])) // !s[j] 表示j結(jié)點(diǎn)在V-U集合中
//t到j(luò)的邊值小于當(dāng)前的最鄰近值
{
lowcost[j] = c[t][j]; //更新j的最鄰近值為t到j(luò)的邊值
closest[j] = t; //更新j的最鄰近點(diǎn)為t
}
}
prim完整代碼
#include <iostream> using namespace std; const int INF = 0x3fffffff; const int N = 100; bool s[N]; int closest[N]; int lowcost[N]; void Prim(int n, int u0, int c[N][N]) { //頂點(diǎn)個(gè)數(shù)n、開(kāi)始頂點(diǎn)u0、帶權(quán)鄰接矩陣C[n][n]//如果s[i]=true,說(shuō)明頂點(diǎn)i已加入最小生成樹(shù)//的頂點(diǎn)集合U;否則頂點(diǎn)i屬于集合V-U//將最后的相關(guān)的最小權(quán)值傳遞到數(shù)組lowcosts[u0] = true; //初始時(shí),集合中U只有一個(gè)元素,即頂點(diǎn)u0int i;int j;for(i = 1; i <= n; i++)//①{if(i != u0) {lowcost[i] = c[u0][i];closest[i] = u0;s[i] = false;}elselowcost[i] =0;}for(i = 1; i <= n; i++) //②{int temp = INF;int t = u0;for(j = 1; j <= n; j++) //③在集合中V-u中尋找距離集合U最近的頂點(diǎn)t{ if((!s[j]) && (lowcost[j] < temp)) {t = j;temp = lowcost[j];}}if(t == u0)break; //找不到t,跳出循環(huán)s[t] = true; //否則,講t加入集合Ufor(j = 1; j <= n; j++) //④更新lowcost和closest{ if((!s[j]) && (c[t][j] < lowcost[j])){lowcost[j] = c[t][j];closest[j] = t; } } }} int main() {int n, c[N][N], m, u, v, w;int u0;cout <<"輸入結(jié)點(diǎn)數(shù)n和邊數(shù)m:"<<endl;cin >> n >> m;int sumcost = 0;for(int i = 1; i <= n; i++) for(int j = 1; j <= n; j++) c[i][j] = INF;cout <<"輸入結(jié)點(diǎn)數(shù)u,v和邊值w:"<<endl;for(int i=1; i<=m; i++) {cin >> u >> v >> w;c[u][v] = c[v][u] = w;}cout <<"輸入任一結(jié)點(diǎn)u0:"<<endl;cin >> u0 ;//計(jì)算最后的lowcos的總和,即為最后要求的最小的費(fèi)用之和Prim(n, u0, c);cout <<"數(shù)組lowcost的內(nèi)容為:"<<endl; for(int i = 1; i <= n; i++) cout << lowcost[i] << " "; cout << endl; for(int i = 1; i <= n; i++) sumcost += lowcost[i]; cout << "最小的花費(fèi)是:" << sumcost << endl << endl; return 0;}總結(jié)
以上是生活随笔為你收集整理的【学习笔记】比较分别用prim和kruskal实现最小生成树和算法优化方案的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 【项目】uniapp前端接收后端spri
- 下一篇: 【笔记】springboot+sprin