1. 程式人生 > >例題:最短網路 圖論演算法之最小生成樹 prim//kruskal 學習筆記

例題:最短網路 圖論演算法之最小生成樹 prim//kruskal 學習筆記

圖論演算法之最小生成樹  prim//kruskal

        最小生成樹簡單的說就是在一個圖裡選取一些邊,使這些邊以及它們所連線的結點組成一棵樹(兩兩結點之間可以到達),並且使選取的邊的邊權最小。         它的成立條件是圖是連通的。並且選取的邊數為n-1。(有n個結點,n-1條邊,只能為一棵樹,沒有別的可能)         主要有兩個演算法:prim和kruskal

prim

【思路】

        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個農場到它本身。
【輸出格式】     只有一個輸出,其中包含連線到每個農場的光纖的最小長度。 【輸入樣例】agrinet.in     4     0  4  9  21     4  0  8  17     9  8  0  16     21 17 16  0 【輸出樣例】agrinet.out 28
【解題思路】 kruskal裸題。。。依然是用標算就能過,,,我閒的沒事粘上來。。。<-_<-