1. 程式人生 > >最小生成樹(二)--prim演算法實現以及堆優化

最小生成樹(二)--prim演算法實現以及堆優化

一、最小生成樹---prim演算法實現

思想:

1、從任意一個頂點開始構造生成樹,假設就從1號頂點吧, 首先將頂點1加入生成樹中,用一個一維陣列book來標記 哪些頂點已經加入了生成樹。
 2、用陣列dis記錄生成樹到各個頂點的距離,最初生成樹中之後1號 頂點,有直連邊時,陣列dis中儲存的就是1號頂點到該頂點 的邊的權值,沒有直連邊的時候就是無窮大,即初始化dis陣列。
 3、從陣列dis中選出離生成樹最近的頂點(假設這個頂點為j) 加入到生成樹中(即在陣列dis中找到最小值)。再以j為中間點, 更新生成樹到每一個非樹頂點的距離(就是鬆弛啦), 即如果dis[k]>e[j][k]則更新dis[k]=e[j][k]。
 4、重複第三步,直到生成樹中有n個頂點為止。
 


程式碼實現:

<span style="font-size:18px;">#include<stdio.h>
int main(void)
{
	int n,m,i,j;
	int k,min;
	int t1,t2,t3;
	int e[7][7],dis[7];
	int book[7]={0};//book陣列初始化 
	int inf=99999999;
	int count=0,sum=0;
	//count用來記錄生成樹中頂點的個數,sum用來儲存路徑之和 
	scanf("%d %d",&n,&m);
	//讀入n,m,n表示頂點的個數,m表示邊的條數。 
	//初始化 
	for(i=1;i<=n;i++)
	{
		for(j=1;j<=n;j++)
		{
			if(i==j)
			{
				e[i][j]=0;
			}
			else
			{
				e[i][j]=inf;
			}
		}
	}
	//開始讀入邊 
	for(i=1;i<=m;i++)
	{
		scanf("%d%d%d",&t1,&t2,&t3);
		//注意無向圖,需要將邊反向在儲存一遍 
		e[t1][t2]=t3;
		e[t2][t1]=t3;
	}
	//初始化dis陣列,這裡是1號頂點到各個頂點的初始距離,因為當前生成樹中只有1號頂點	
	for(i=1;i<=n;i++)
	{
		dis[i]=e[1][i];
	}
	//將1號頂點加入生成樹,這裡book來標記一個頂點是否已經加入生成樹 
	book[1]=1;
	count++;
	while(count<n)
	{
		min=inf;
		for(i=1;i<=n;i++)
		{
			if(book[i]==0&&dis[i]<min)
			{
				min=dis[i];
				j=i;
			}
		}
		book[j]=1;
		count++;
		sum=sum+dis[j];
		//掃描當前頂點j所有的邊,再以j為中間點,更新生成樹到每一個非樹頂點的距離; 
		for(k=1;k<=n;k++)
		{
			if(book[k]==0&&dis[k]>e[j][k])
			{
				dis[k]=e[j][k];
			} 
		}
	}
	printf("%d\n",sum);
	return 0;	
} 
</span>

二、最小生成樹—prim演算法的堆優化

需要用到三個陣列
1、陣列dis用來記錄生成樹到各個頂點的距離
2、陣列h是一個最小堆,堆裡面儲存的是頂點編號。(請注意,這裡並不是按照頂點編號的大小來建立最小堆的,而是按照頂點在陣列dis中所對應的值來建立這個最小堆的)
3、pos陣列來記錄每個頂點在最小堆中的位置


例如,下圖中
下面最小堆中的圓圈中儲存的是頂點編號,圓圈右下角的數是該頂點(圓圈裡面的數)到生成樹的最短距離,即陣列dis中儲存的值 


程式碼實現:

<span style="font-size:18px;">#include<stdio.h>
int dis[7],book[7]={0};
//book陣列用來記錄哪些頂點已經放入生成樹中 
int h[7],pos[7],size; 
//h陣列用來儲存堆,pos陣列用來儲存每個頂點在堆中的位置,size為堆的大小 
//交換函式,用來交換堆中的兩個元素的值 
void swap(int x,int y)
{
	int t;
	t=h[x];
	h[x]=h[y];
	h[y]=t;
	//同步更新pos 
	t=pos[h[x]];
	pos[h[x]]=pos[h[y]];
	pos[h[y]]=t;
} 
//向下調整函式,傳入一個需要向下調整的結點編號 
void siftdown(int i)
{
	int t,flag=0;
	//flag用來標記是否需要繼續向下調整 
	while(i*2<=size&&flag==0)
	{
		//比較i和它左兒子i*2在dis中的值,並用t記錄值較小的結點編號 
		if(dis[h[i]]>dis[h[i*2]])
		{
			t=i*2;
		}
		else
		{
			t=i;
		}
		//如果它有右兒子,再對右兒子進行討論 
		if(t*2+1<=size)
		{
			//如果右兒子的值更小,更新較小的結點編號 
			if(dis[h[t]]>dis[h[i*2+1]])
			{
				t=i*2+1;
			}
		}
		//如果發現最小的結點編號不是自己,說明子結點中有比父結點更小的 
		if(t!=i)
		{
			//交換它們 
			swap(t,i);
			//更新i為剛才與它交換的兒子結點的編號,便於接下來繼續向下調整 
			i=t;
		}
		else
		{
			//否則說明當前的父結點已經比兩個子結點都要小了,不需要再進行調整了 
			flag=1;
		}
	}
}
//傳入一個需要向上調整的結點編號i 
void siftup(int i)
{
	int flag=0;
	//flag用來標記是否需要繼續向上調整 
	if(i==1)
	{
		//如果是堆頂,就返回,不需要調整 
		return ;
	}
	//不在堆頂,並且當前結點i的值比父結點小的時候繼續向上調整 
	while(i!=1&&flag==0)
	{
		//判斷是否比父結點的小 
		if(dis[h[i]]<dis[h[i/2]])
		{
			//交換它和它父親的位置 
			swap(i,i/2);
		}
		else
		{
			//表示已經不需要調整了,當前結點的值比父結點的值要大 
			flag=1;
		}
		//更新編號i為它父結點的編號,從而便於下一次繼續向上調整 
		i=i/2;
	}
}
//從堆頂取出一個元素 
int pop()
{
	int t;
	t=h[1];//用一個臨時變數記錄堆頂點的值 
	pos[t]=0;//其實這句話可以不要 
	h[1]=h[size];//將堆的最後一個點賦值到堆頂 
	pos[h[1]]=1;
	size--;//堆的元素減少1 
	siftdown(1);//向下調整 
	return t;//返回之前記錄的堆頂點 
}
int main(void)
{
	int n,m,i,j,k;
	//u,v,w和next的陣列大小要根據實際情況來設定,此圖是無向圖,要比2*m的最大值要大1 
	//first要比n的最大值要大1,要比2*m的最大值要大1 
	int u[19],v[19],w[19],first[7],next[19];
	int inf=99999999;
	//count用來記錄生成樹中頂點的個數,sum用來記錄儲存路徑之和 
	int count=0,sum=0;
	//讀入n和m,n表示頂點個數,m表示邊的條數 
	scanf("%d %d",&n,&m);
	//開始讀入邊 
	for(i=1;i<=m;i++)
	{
		scanf("%d%d%d",&u[i],&v[i],&w[i]);
	}
	//這裡是無向圖,所以需要將所有的邊再反向儲存一次 
	for(i=m+1;i<=2*m;i++)
	{
		u[i]=v[i-m];
		v[i]=u[i-m];
		w[i]=w[i-m];
	}
	//開始使用鄰接表儲存邊 
	for(i=1;i<=n;i++)
	{
		first[i]=-1;
	}
	for(i=1;i<=2*m;i++)
	{
		next[i]=first[u[i]];
		first[u[i]]=i;
	}
	//將1號頂點加入生成樹,book陣列來標記一個頂點是否已經加入生成樹 
	book[1]=1;
	count++;
	//初始化dis陣列,這裡是1號頂點到其餘各個頂點的初始距離 
	dis[1]=0;
	for(i=2;i<=n;i++)
	{
		dis[i]=inf;
	}
	k=first[1];
	while(k!=-1)
	{
		dis[v[k]]=w[k];
		k=next[k];
	} 
	//初始化堆 
	size=n;
	for(i=1;i<=size;i++)
	{
		h[i]=i;
		pos[i]=i;
	}
	for(i=size/2;i>=1;i--)
	{
		siftdown(i);
	}
	//先彈出一個堆頂元素,因為此時堆頂是1號頂點 
	pop();
	while(count<n)
	{
		j=pop();
		book[j]=1;
		count++;
		sum=sum+dis[j];
		//掃描當前頂點j所有的邊,再以j為中間結點,進行鬆弛 
		k=first[j];
		while(k!=-1)
		{
			if(book[v[k]]==0&&dis[v[k]]>w[k])
			{
				//更新距離 
				dis[v[k]]=w[k];
				//對該點在堆中進行向上調整
				//pos[v[k]]儲存的是頂點v[k]在堆中的位置 
				siftup(pos[v[k]]); 
			}
			k=next[k];
		}
	}
	printf("%d\n",sum);
	return 0;
}</span>

相關推薦

小生成樹--prim演算法實現以及優化

一、最小生成樹---prim演算法實現 思想: 1、從任意一個頂點開始構造生成樹,假設就從1號頂點吧, 首先將頂點1加入生成樹中,用一個一維陣列book來標記 哪些頂點已經加入了生成樹。  2、用陣列dis記錄生成樹到各個頂點的距離,最初生成樹中之後1號 頂點,有直連邊時,

演算法導論--小生成樹Kruskal和Prim演算法

關於圖的幾個概念定義: 連通圖:在無向圖中,若任意兩個頂點vi與vj都有路徑相通,則稱該無向圖為連通圖。 強連通圖:在有向圖中,若任意兩個頂點vi與vj都有路徑相通,則稱該有向圖為強連通圖。 連通網:在連通圖中,若圖的邊具有一定的意義,每一條邊都對應

小生成樹1--Kruskal演算法

圖的最小生成樹 圖的最小生成樹,是指用最小的邊讓圖連通,讓任意兩點之間可以互相到達。圖如果有n個頂點,則應該有n-1條邊。此時連通無向圖沒有迴路,就是一顆樹,所以稱為最小生成樹。 最小生成樹是讓邊的總長度之和最短,其中一種方法是可以選擇最短的邊,然後依次

【數據結構】 小生成樹——kruskal算法

適用於 相同 inf prim 什麽 一段 大樹 集合 n-1   上一期說完了什麽是最小生成樹,這一期咱們來介紹求最小生成樹的算法:kruskal算法,適用於稀疏圖,也就是同樣個數的節點,邊越少就越快,到了數據結構與算法這個階段了,做題靠的就是速度快,時間復雜度小。   

【數據結構】 小生成樹——prim算法

copy cost 是否 不用 wait 大樹 分享圖片 pri clas   上一期介紹到了kruskal算法,這個算法誕生於1956年,重難點就是如何判斷是否形成回路,此處要用到並查集,不會用當然會覺得難,今天介紹的prim算法在kruskal算法之後一年(即1957年

python機器學習案例系列教程——小生成樹MSTPrim演算法和Kruskal演算法

最小生成樹MST 一個有 n 個結點的連通圖的生成樹是原圖的極小連通子圖,且包含原圖中的所有 n 個結點,並且有保持圖連通的最少的邊。 也就是說,用原圖中有的邊,連線n個節點,保證每個節點都被連線,且使用的邊的數目最少。 最小權重生成樹 在一給定

小生成樹Dijkstra演算法短路Prim演算法的異同

Prim演算法用於構建最小生成樹——即樹中所有路徑之和最小,但不能保證任意兩點之間是最短路徑。例如,構建電路板,使所有邊的和花費最少。只能用於無向圖。Dijkstra演算法用於構建(MST)——即樹中指

小生成樹普利姆演算法、克魯斯卡爾演算法

設G = (V,E)是無向連通帶權圖,即一個網路。E中的每一條邊(v,w)的權為c[v][w]。如果G的子圖G’是一棵包含G的所有頂點的樹,則稱G’為G的生成樹。生成樹上各邊權的總和稱為生成樹的耗費。在G的所有生成樹中,耗費最小的生成樹稱為G的最小生成樹。構造最小生成樹的兩種方

小生成樹MST的性質及演算法 [轉】

轉自: 最小生成樹性質1:設G=(V,E)是一個連通網路,U是頂點集V的一個真子集。若(u,v)是G中所有的一個端點在U(u∈U)裡、另一個端點不在U(即v∈V-U)裡的邊中,具有最小權值的一條邊,則一定存在G的一棵最小生成樹包括此邊(u,v)。 證明: 為方便說明

POJ 2485 Highways 小生成樹 Kruskal

between all pac pair content cross cte reel sca Description The island nation of Flatopia is perfectly flat. Unfortunately, Flatopia

還是暢通工程——小生成樹王道

不一定 要求 LG sin 最小生成樹 bsp 生成樹 結束 operator 題目描述: 某省調查鄉村交通狀況,得到的統計表中列出了任意兩村莊間的距離。省政府“暢通工程”的目標是使全省任何兩個村莊間都可以實現公路交通(但不一定有直接的公路相連,只要能間接通過公路可

【複習】---【p2820】區域網--洛谷//小生成樹 2

題目背景某個區域網內有n(n<=100)臺計算機,由於搭建區域網時工作人員的疏忽,現在區域網內的連線形成了迴路,我們知道如果區域網形成迴路那麼資料將不停的在迴路內傳輸,造成網路卡的現象。因為連線計算機的網線本身不同,所以有一些連線不是很暢通,我們用f(i,j)表示i,j之間連線的暢通程度,f(i,j)值

小生成樹模板

luogu 3366 模板如下: #include <bits/stdc++.h> #define ll long long #define N 200005 using namespace std; int fa[N]; struct node { int l,r,va

資料結構——圖8——小生成樹MST

問題的提出 如下圖,假設這裡有一系列的房屋,問如何鋪設電線,可以使得連線所有房屋的電線的總成本最低?這是20世紀20年代早期研究最小生長樹的最初動機。 (捷克數學家OtakarBorůvka完成的工作)。 最短路徑樹與最小生成樹(MST) 上次,我們看到了Dijkstra演

小生成樹MST之Kruskal

題目大意 給定一個 n n n個點m條邊的無向圖

小生成樹MST

目錄 一、知識點 二、例題 一、知識點 1. 生成樹定義:在一個有n個點的無向連通圖中,取其n-1條邊並連線所有的頂點,所得到的子圖稱為原圖的一棵生成樹。 2. 樹的屬性:無環+連通+任意兩點之間只有唯一的簡單路徑+刪掉任意邊就不連通 3. 最小生成樹:各邊權和最小的一棵

資料結構 筆記:小生成樹Kruskal

最小生成樹的特徵: -選取的邊是圖中權值較小的邊 -所有邊連線後不構成迴路 既然最小生成樹關心的是如何選擇n-1條邊,那麼是否可以直接以邊為核心進行演算法設計? -由4個頂點構成圖,選擇3條權值最小的邊 如何判斷新選擇的邊與已選擇的邊是否構成迴路? 技巧:前驅標記陣列 -

小生成樹Kruskal

#include<iostream> #include<cstdio> #include<vector> #include<algorithm> using namespace std; int flag = 0; struct

動態維護小生成樹IOI2003Maintain

WOJ2235 Maintain 描述 農夫約翰的奶牛們希望能夠在農場的N(1<=N<=200)塊田地中自由的旅遊,儘管這些田地被樹林分開了。他們希望能夠通過維護一對對田地間的路徑使得任意兩塊田地間都有通路。奶牛們可以沿著任一方向的維護的路徑旅遊。

資料結構作業15—圖的遍歷與小生成樹選擇題

2-1給定有權無向圖如下。關於其最小生成樹,下列哪句是對的? (3分) A.邊(B, F)一定在樹中,樹的總權重為23 B.邊(H, G)一定在樹中,樹的總權重為20 C.最小生成樹唯一,其總權重為20 D.最小生成樹不唯一,其總權重為23