例題:最短網路 圖論演算法之最小生成樹 prim//kruskal 學習筆記
阿新 • • 發佈:2019-01-09
圖論演算法之最小生成樹 prim//kruskal
最小生成樹簡單的說就是在一個圖裡選取一些邊,使這些邊以及它們所連線的結點組成一棵樹(兩兩結點之間可以到達),並且使選取的邊的邊權最小。 它的成立條件是圖是連通的。並且選取的邊數為n-1。(有n個結點,n-1條邊,只能為一棵樹,沒有別的可能) 主要有兩個演算法:prim和kruskalprim
【思路】
prim演算法寫起來和dijkstra很像,因為他們的大體思路是一樣的。s[i]表示與i相連的所有邊的最小值(條件是這條邊的另一端點已經是之前擴充套件過的)。每一次找到s值最小的點,用它去擴充套件和他相連的所有的邊的另一結點,經過n-1此操作,就可以完成對所有點和邊的擴充套件。【程式碼】
#include<iostream> #include<cstdio> #include<cstring> using namespace std; int n,i,j,Min,minj,tot; int a[105][105],s[105]; bool b[105]; int main() { scanf("%d",&n); for (i=1;i<=n;++i) for (j=1;j<=n;++j) scanf("%d",&a[i][j]); for (i=1;i<=n;++i) s[i]=0x7777777; s[1]=0; for (i=1;i<=n-1;++i) { Min=0x7777777; for (j=1;j<=n;++j) if (!b[j]&&s[j]<Min) { Min=s[j]; minj=j; } b[minj]=true; for (j=1;j<=n;++j) if (!b[j]&&a[minj][j]<s[j]) s[j]=a[minj][j]; } for (i=1;i<=n;++i) tot+=s[i]; printf("%d",tot); return 0; }
【求出最小生成樹中的所有邊】
也不是很難想,只要再加一個前驅就行了,最後遞迴輸出;【程式碼】
#include<iostream> #include<cstdio> #include<cstring> using namespace std; int n,i,j,Min,minj,tot; int a[105][105],s[105],pre[105]; bool b[105]; void print(int x) { if (pre[x]==0) { printf("%d\n",x); return; } print(pre[x]); printf("%d\n",x); return; } int main() { scanf("%d",&n); for (i=1;i<=n;++i) for (j=1;j<=n;++j) scanf("%d",&a[i][j]); for (i=1;i<=n;++i) s[i]=0x7777777; s[1]=0; for (i=1;i<=n-1;++i) { Min=0x7777777; for (j=1;j<=n;++j) if (!b[j]&&s[j]<Min) { Min=s[j]; minj=j; } b[minj]=true; for (j=1;j<=n;++j) if (!b[j]&&a[minj][j]<s[j]) { s[j]=a[minj][j]; pre[j]=minj; } } for (i=1;i<=n;++i) tot+=s[i]; printf("%d\n",tot); print(n); return 0; }
例:最優佈線問題
【問題描述】 學校有n臺計算機,為了方便資料傳輸,現要將它們用資料線連線起來。兩臺計算機被連線是指它們間有資料線連線。由於計算機所處的位置不同,因此不同的兩臺計算機的連線費用往往是不同的。 當然,如果將任意兩臺計算機都用資料線連線,費用將是相當龐大的。為了節省費用,我們採用資料的間接傳輸手段,即一臺計算機可以間接的通過若干臺計算機(作為中轉)來實現與另一臺計算機的連線。 現在由你負責連線這些計算機,任務是使任意兩臺計算機都連通(不管是直接的或間接的)。 【輸入格式】 輸入檔案wire.in,第一行為整數n(2<=n<=100),表示計算機的數目。此後的n行,每行n個整數。第x+1行y列的整數表示直接連線第x臺計算機和第y臺計算機的費用。 【輸出格式】 輸出檔案wire.out,一個整數,表示最小的連線費用。 【輸入樣例】 3 0 1 2 1 0 1 2 1 0 【輸出樣例】 2 (注:表示連線1和2,2和3,費用為2) 【解題思路】 prim裸題,直接用上面給的標算都能過。。在此就不粘程式碼了->_->kruskal
【演算法描述】
需要用到並查集,具體過程如下: 1.初始化並查集。father[x]=x。 2.tot=0 3.將所有邊用快排從小到大排序。 4.計數器 k=0; 5.for (i=1; i<=M; i++) //迴圈所有已從小到大排序的邊 if 這是一條u,v不屬於同一集合的邊(u,v)(因為已經排序,所以必為最小), begin ①合併u,v所在的集合,相當於把邊(u,v)加入最小生成樹。 ②tot=tot+W(u,v) ③k++ ④如果k=n-1,說明最小生成樹已經生成,則break; end; 6. 結束,tot即為最小生成樹的總權值之和。【程式碼】
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct hp{
int x,y,s;
}a[10005];
int n,i,j,dis,num,tot,k;
int father[10005];
int cmp(hp a,hp b)
{
return a.s<b.s;
}
int find(int x)
{
if (x==father[x]) return father[x];
father[x]=find(father[x]);
return father[x];
}
void merge(int x,int y)
{
int f1=find(x);
int f2=find(y);
father[f1]=f2;
return;
}
int main()
{
scanf("%d",&n);
for (i=1;i<=n;++i)
for (j=1;j<=n;++j)
{
scanf("%d",&dis);
if (dis!=0)
{
num++;
a[num].x=i;
a[num].y=j;
a[num].s=dis;
}
}
for (i=1;i<=n;++i)
father[i]=i;
sort(a+1,a+num+1,cmp);
for (i=1;i<=num;++i)
{
if (find(a[i].x)!=find(a[i].y))
{
merge(a[i].x,a[i].y);
tot+=a[i].s;
k++;
}
if (k==n-1) break;
}
printf("%d",tot);
return 0;
}
【記錄路徑】
其實並沒有什麼好說的,加一個bool或者別的什麼記錄一下,最後按順序輸出即可;例:最短網路
【問題描述】 農民約翰被選為他們鎮的鎮長!他其中一個競選承諾就是在鎮上建立起網際網路,並連線到所有的農場。當然,他需要你的幫助。約翰已經給他的農場安排了一條高速的網路線路,他想把這條線路共享給其他農場。為了用最小的消費,他想鋪設最短的光纖去連線所有的農場。你將得到一份各農場之間連線費用的列表,你必須找出能連線所有農場並所用光纖最短的方案。每兩個農場間的距離不會超過100000。 【輸入格式】第一行: | 農場的個數,N(3<=N<=100)。 |
第二行..結尾 | 後來的行包含了一個N*N的矩陣,表示每個農場之間的距離。理論上,他們是N行,每行由N個用空格分隔的陣列成,實際上,他們限制在80個字元,因此,某些行會緊接著另一些行。當然,對角線將會是0,因為不會有線路從第i個農場到它本身。 |
【解題思路】 kruskal裸題。。。依然是用標算就能過,,,我閒的沒事粘上來。。。<-_<-