1. 程式人生 > >Dijkstra和Prime&Kruskal演算法小結

Dijkstra和Prime&Kruskal演算法小結

前言

講解這兩個經典演算法的博文已經汗牛充棟了,這裡再記錄一下自己學習的心得,歡迎指正。

Dijkstra

這是一個求某一個端點到其他所有端點的最短距離的演算法,有貪心加深搜演算法的特點。即在每一層都選現已知距離最短的點,確定下來,然後以此為基礎更新未確定距離的點,再進行下一輪搜尋。
其中貪心的合理性是因為:如果某頂點到src(出發點)的距離是所有未確定的點中最小的,那麼就可以將之確定下來了,後面不會出現更優解–以另一個頂點為中間點過來–因為那些尚未確定距離點將來的結果路線,一定是藉助之前就確定了最優解的點完成的。
這裡貪心的特點是不會產生狀態回退的情況,有也是隻有一步回退。
示例資料:

6 8  (六個頂點(從1開始計數),八個邊)
1 3 10
1 5 30
1 6 100
2 3 5
3 4 50
4 6 10
5 4 20
5 6 60
結果
id: 1 closest distance: 0
id: 2 closest distance: 4095(INF)
id: 3 closest distance: 10
id: 4 closest distance: 50
id: 5 closest distance: 30
id: 6 closest distance: 60

實現過程中,關鍵點是三個:1.標記一個頂點的最短距離是否已經確定2.更新未確定點的距離3.更新後再排序
一開始寫犯得錯誤是,為了便捷的實現功能3,用優先佇列,結果給另外兩個功能的實現造成了麻煩。這裡採用兩個一維陣列來處理就比較合適,用struct比較麻煩。一個來標記是否確定了距離,一個記錄現在的距離。
判斷結束點時用了一個trick,融入到了找最小值的過程中,若所有的點的最小距離都已經確定,包括那些到達不了的。這樣沒有那個點的距離是大於INF(假定的最大值)了,便可依次為標誌結束迴圈。

#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#define MAX 100
#define INF 0xfff
using namespace std;


int main(int argc, char const *argv[])
{
	int V, E, a, b, len;
	int atlas[MAX][MAX];
	int distance[MAX];
	bool designed[MAX];
	cin>>V>>
E; fill(distance, distance+V+1, INF); fill(designed, designed+V, false); for (int i=0;i<MAX;i++){ for (int j=0;j<MAX;j++){ if (i == j) atlas[i][j] = 0; else atlas[i][j] = INF; } } for (int i = 0; i < E; ++i) { cin>>a>>b>>len; atlas[a][b] = len; } int src = 0;//設出發點為端點1(從1開始計數) distance[src] = 0; int d; while(true){ d = V; for (int i = 0; i < V; ++i) { if (!designed[i] && distance[i]<distance[d]) d = i; } if (d == V) break; for (int i = 0; i < V; ++i) { distance[i] = min(distance[i], distance[d]+atlas[d+1][i+1]); } designed[d] = true; } for (int i = 0; i < V; ++i) { cout<<"id: "<<i+1<<" closest distance: "<<distance[i]<<endl; } return 0; }

Prime和Kruskal

Prime和Kruskal演算法是最小連通圖生成演算法,其中Prime是通過不斷擴充套件已接入連通圖的頂點的集合來完成連通圖的構建。Kruskal是通過不斷尋找最短的不會導致形成環路的邊來構建連通圖。
測試資料:六個頂點,十個邊,無向圖。

6 10
1 2 6
1 3 1
1 4 5
2 3 5
2 5 3
3 4 5
3 5 6
3 6 4
4 6 2
5 6 6

Prime演算法實現時的要點有1.實現已加入連通圖頂點集合和未加入連通圖頂點集合的增刪2.遍歷兩個集合間所有邊,找出最小值
這裡還是用一個bool陣列維護即可,標記是否加入聯通圖,現在看來用其他結構還是比較麻煩,待後來有什麼新發現吧。

#include <iostream>
#include <algorithm>
#define MAX 100
#define INF 0xff
using namespace std;

int main(int argc, char const *argv[])
{
   int atlas[MAX][MAX] = {0};
   bool join[MAX];
   int E, V, a, b, dis;

   cin>>V>>E;
   for (int i = 1; i <= V; ++i)
   {
   	for (int j = 1; j <= V; ++j)
   	{
   		if (i == j) atlas[i][j] = 0;
   		else atlas[i][j] = INF;
   	}
   }
   for (int i = 0; i < E; ++i)
   {
   	cin>>a>>b>>atlas[a][b];//直接輸入:P
   	atlas[b][a] = atlas[a][b];
   }
   
   fill(join, join+V, false);
   join[1] = true;
   int min, extand, cost=0, sum;
   while(true){
   	min = INF;
   	sum = 0;
   	//兩層迴圈遍歷一下
   	for (int i = 1; i <= V; ++i)
   	{
   		if (join[i])
   		for (int j = 1; j <= V; ++j){
   			if (!join[j] && min > atlas[i][j]){
   				min = atlas[i][j];
   				extand = j;
   			}
   				
   		}
   	}
   	cout<<"extanding: "<<extand<<" costing: "<<min<<endl;
   	join[extand] = true;
   	cost += min;
   	//檢查是否全為true了,即是否都加入連通圖了
   	for (int i = 1; i <= V; ++i){
   		sum += join[i];
   	}
   	if (sum == V)
   		break;
   }
   cout<<"Costing: "<<cost<<endl;


   return 0;
}

Krustal演算法
這裡的貪心key值是邊的長度,排序後從小到大來新增,新增時注意檢查是否生成了環路。關鍵點就是檢查是否產生了環路,這裡用並查集實現。
並查集是一種樹結構,同一個集合的元素都掛到一個樹裡面,有共同根節點的元素即為同一個集合中的。這裡每連一條邊,都更新一下集合,用pre表示該元素的父節點,其中父節點的pre值是其本身。並且做一下狀態壓縮,將樹的深度降低為一,減少查詢時間。

#include <iostream>
#include <algorithm>
#include <stdio.h>
#define MAX 100
#define INF 0xfff
using namespace std;

int pre[MAX] = {0};
struct edge{
	int a, b, dis;
	edge(){
		a = b = 0;
		dis = INF;
	}
};

bool cmp(edge a, edge b){
	return a.dis < b.dis;
}

int find(int x){
	int r = x;
	while (r != pre[r]) r = pre[r];
	int i = x, j;
	while (i != pre[i]){//下面這一塊就是狀態壓縮,讓子樹中的每個節點都直接指向根節點
		j = pre[i];
		pre[i] = r;
		i = j;
	}
	return r;
}

void join(int a, int b){
	int fa = find(a);
	int fb = find(b);
	if (fa != fb)
		pre[fa] = fb;//子集合並,注意是把子集的父節點進行合併,不能直接把自己給並過去了
}
int main(int argc, char const *argv[])
{
	//definition
	int V, E, cost=0;
	edge edges[MAX*MAX];
	cin>>V>>E;
	//input
	for (int i = 0; i < E; ++i)
	{
		cin>>edges[i].a>>edges[i].b>>edges[i].dis;//賦值給結構陣列
	}
	sort(edges, edges+E, cmp);
	for (int i = 1; i <= V; ++i)
	{
		pre[i] = i;
	}

	for (int i = 0; i < E; ++i)
	{
		edge e = edges[i];
		if (find(e.a) != find(e.b)){
			join(e.b, e.a);
			cost += e.dis;
		}
	}
	printf("Costing: %d\n", cost);
	return 0;
}