(C/C++)-最小生成樹算法(Prim&Kruskal)和單源最短路徑算法(Dijkstra)

(C/C++)-最小生成樹算法(Prim&Kruskal)和單源最短路徑算法(Dijkstra)

1、什麼是最小生成樹

對於一個帶權連通無向圖G=(V,E),圖G的不同的生成樹,其所對應的生成樹的權值可能不同。設R是G所有生成樹的集合,T是R中權值最小的那棵生成樹,則稱T爲G的最小生成樹

最小生成樹的性質

  • 最小生成樹可能不唯一,但其對應的邊的權值之和總是唯一的,且是最小的;
  • 最小生成樹的邊數爲頂點數-1;

2、Prim算法的實現(選點)

圖G=(V,E),其中V爲所有結點的集合,E爲圖G的邊的集合
用輔助集合s,來描述被選中的頂點

思路:

  • 首先將第一個頂點放入集合s中,即s={1};
  • 做如下貪心選擇,當s是v的真子集時,選取i屬於s,j屬於V-S,且c[i][j]是最小的邊,並將頂點j加入到集合s中;
  • 當S=V時選取結束,此時就生成了一棵最小生成樹;

如圖所示,描述了Prim算法選取頂點的過程:
在這裏插入圖片描述
具體實現過程:

void prim(int c[N][N], int n)
{
   
    
	int lowcost[N];//存儲非集合s頂點到集合s中頂點最小邊的權值
	int closest[N];//存儲非集合s頂點到集合s中權值最小邊的頂點
	int s[N];

	s[1] = 1;
	printf("1 ");

	//lowcost[]和closest[]的初始化
	for (int i = 2; i < N; i++)
	{
   
    
		closest[i] = 1;
		lowcost[i] = c[1][i];
		s[i] = 0;
	}

	//每次循環箱集合s中添加一個頂點
	for (int i = 2; i < N; i++)
	{
   
    
		int min = maxint;
		int temp = 1;//用來記錄選擇的頂點

		//做貪心選擇,選擇非集合s中頂點到s中頂點最小的權值邊的頂點
		for (int j = 2; j < N; j++)
			if (!s[j] && min > lowcost[j])
			{
   
    
				//更新值,並記錄
				min = lowcost[j];
				temp = j;
			}

		s[temp] = 1;//頂點加入到集合s
		printf("%d ", temp);

		//更新lowcost[]和closest[]
		for(int j=2;j<N;j++)
			if (!s[j] && lowcost[j] > c[j][temp])
			{
   
    
				lowcost[j] = c[j][temp];
				closest[j] = temp;
			}
	}
}

在main()函數中測試一下Prim算法:

int main()
{
   
    
	int c[N][N] = {
   
    //0號下標不用
		{
   
    maxint,maxint,maxint,maxint,maxint,maxint,maxint},//0
		{
   
    maxint,maxint,6,1,5,maxint,maxint},//1
		{
   
    maxint,6,maxint,5,maxint,3,maxint},//2
		{
   
    maxint,1,5,maxint,5,6,4},//3
		{
   
    maxint,5,maxint,5,maxint,maxint,2},//4
		{
   
    maxint,maxint,3,6,maxint,maxint,6},//5
		{
   
    maxint,maxint,maxint,4,2,6,maxint}//6
	};

	printf("Prim點選取順序爲:\n");
	prim(c, N);

	return 0;
}

在這裏插入圖片描述


3、Kruskal算法的實現(選邊)

圖G=(V,E),其中V爲所有結點的集合,E爲圖G的邊的集合

採用Kruskal算法時,採用了並查集作爲輔助的數據結構
並查集中兩個重要的實現函數:

  • int find_root(int x):查找以結點x的根結點;
  • int union_set(int x,int y):合併x,y所在的兩個子集,若兩個子集存在迴路返回0,不存在迴路返回1;

思路:

  • 將圖G的邊集按照權值大大小升序排列
  • 每次選取未被選取過,且權值最小的邊,若選取該邊構成迴路,則捨棄該邊,選擇下一條權值最小的邊;
  • 直到圖G所有的頂點在一個連通分量上;

如圖所示,描述了Kruskal算法選取頂點的過程:
在這裏插入圖片描述
具體實現過程:

邊的結構體

typedef struct edge
{
   
     
	int x;
	int y;
	int weight;
	char name;
	int selected;
}Edge;

並查集的函數

int parent[N];//記錄頂點的父結點
int rank[N];//記錄以頂點爲根的樹的深度(壓縮路徑)

//初始化parent[]和rank[]
void init()
{
   
     
	for (int i = 1; i < N; i++)
	{
   
     
		parent[i] = -1;//初始時,每個頂點的父結點默認爲-1
		rank[i] = 1;
	}
}

//查找頂點x的根結點
int find_root(int x)
{
   
     
	int x_root = x;
	while (parent[x_root] != -1)
		x_root = parent[x_root];
	return x_root;
}

//合併兩個頂點所在的子集,合併成功返回1,失敗返回0
int union_set(int i, int j)
{
   
     
	int i_root = find_root(i);
	int j_root = find_root(j);

	if (i_root == j_root)
		return 0;//i,j在同一個子集中,合併失敗

	//i,j不在同一個子集中,合併兩個子集
	if (rank[i_root] > rank[j_root])
		parent[j_root] = i_root;
	else if (rank[i_root] < rank[j_root])
		parent[i_root] = parent[j_root];
	else
	{
   
     
		//i,j深度相同的情況下
		parent[i_root] = j_root;
		rank[j_root]++;
	}
	return 1;
}

Kruskal實現

//n是邊的個數
void kruskal(Edge e[], int n)
{
   
     
	init();
	qsort(e, 11, sizeof(e[1]), cmp);//升序

	for (int i = 1; i <= n; i++)
	{
   
     
		int x = find_root(e[i].x);
		int y = find_root(e[i].y);

		if (x != y)
		{
   
     
			//無迴路,選中此邊
			e[i].selected = 1;
			union_set(e[i].x, e[i].y);
			printf("%c ", e[i].name);
		}
	}
	/*
	for (int i = 1; i <= n; i++)
		if (e[i].selected)
			printf("%c ", e[i].name);*/
}

測試函數

int main()
{
   
     
	Edge e[11] = {
   
     //10條邊
		{
   
     0},//此條數據不用
		{
   
     1,4,5,'a',0},{
   
     4,6,2,'b',0},{
   
     6,5,6,'c',0},{
   
     5,2,3,'d',0},{
   
     2,1,6,'e',0},
		{
   
     1,3,1,'f',0},{
   
     3,4,5,'g',0},{
   
     3,6,4,'h',0},{
   
     3,5,6,'i',0},{
   
     3,2,5,'j',0}
	};

	int n = 10;

	kruskal(e, n);

	return 0;
}

在這裏插入圖片描述


4、Dijkstra算法的實現(選點)

圖G=(V,E),其中V爲所有結點的集合,E爲圖G的邊的集合

採用輔助數組dist[i],來存儲從源點到i的最短距離

思路:

  • 從集合V-S中選取頂點j,滿足dist[j]=min{dist[i]|i屬於V-S},並將頂點j加入到集合s中
  • 集合s擴充後,修改源點出發到V-S上任一頂點k的最短路徑,dist[j]+c[j][k]<dist[k](這裏是Dijkstra區別於Prim的最重要一點)
  • 重複上述步驟,知道所有頂點都在集合S中

如圖示

在這裏插入圖片描述
具體實現:

//dist[i]存儲從源點到i的最短路徑
void dijkstra(int c[][N], int dist[], int start)
{
   
      
	int s[N];

	//初始化參數
	for (int i = 1; i < N; i++)
	{
   
      
		dist[i] = c[start][i];
		s[i] = 0;
	}
	s[start] = 1;

	//每次循環箱集合s中添加一個頂點
	for (int i = 2; i < N; i++)
	{
   
      
		int tmp = maxint;
		int u = start;

		//做貪心選擇,選擇非集合s中頂點到源點最小的權值邊的頂點
		for (int j = 1; j < N; j++)
		{
   
      
			if (!s[j] && dist[j] < tmp)
			{
   
      
				//記錄
				u = j;
				tmp = dist[j];
			}
		}

		s[u] = 1;

		//更新dist[]的值
		for (int j = 1; j < N; j++)
			if (!s[j] && (dist[u] + c[u][j] < dist[j]))
				dist[j] = dist[u] + c[u][j];

	}
}

main函數中的測試:

int main()
{
   
      
	int c[N][N] = {
   
      
		{
   
      0},//0號下標不使用
		{
   
      0,0,10,maxint,maxint,5},
		{
   
      0,maxint,0,1,maxint,2},
		{
   
      0,maxint,maxint,0,4,maxint},
		{
   
      0,7,maxint,6,0,maxint},
		{
   
      0,maxint,3,9,2,0}
	};//5個頂點,0號下標不使用

	int dist[N], start = 1;

	dijkstra(c, dist, start);

	for (int i = 1; i < N; i++)
		printf("dist[%d]=%d\t", i, dist[i]);
	printf("\n");

	return 0;
}

在這裏插入圖片描述


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章