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 
#include 
#include 
#include 
#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 
#include 
#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 
#include 
#include 
#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;
}

你可能感兴趣的:(算法)