Dijkstra和Prim算法的区别
1.先说说prim算法的思想:
众所周知,prim算法是一个最小生成树算法,它运用的是贪心原理(在这里不再证明),设置两个点集合,一个集合为要求的生成树的点集合A,另一个集合为未加入生成树的点B,它的具体实现过程是:
第1步:所有的点都在集合B中,A集合为空。
第2步:任意以一个点为开始,把这个初始点加入集合A中,从集合B中减去这个点(代码实现很简单,也就是设置一个标示数组,为false表示这个点在B中,为true表示这个点在A中),寻找与它相邻的点中路径最短的点,如后把这个点也加入集合A中,从集合B中减去这个点(代码实现同上)。
第3步:集合A中已经有了多个点,这时两个集合A和B,只要找到A集合中的点到B集合中的点的最短边,可以是A集合中的与B集合中的点的任意组合,把这条最短边有两个顶点,把在集合B中的顶点加入到集合A中,(代码实现的时候有点技巧,不需要枚举所有情况,也就是更新操作)。
第4步:重复上述过程。一直到所有的点都在A集合中结束。
2.说说dijkstra算法的过程:
这个算法的过程有比prim算法的过程稍微多一点点步骤,但是思想确实巧妙的,也是贪心原理,它的目的是求某个源点到目的点的最短距离,总的来说,dijkstra算法也就是求某个源点到目的点的最短路,求解的过程也就是求源点到整个图的最短距离,次短距离,第三短距离等等(这些距离都是源点到某个点的最短距离)。。。。。求出来的每个距离都对应着一个点,也就是到这个点的最短距离,求的也就是原点到所有点的最短距离,并且存在了一个二维数组中,最后给出目的点就能直接通过查表获得最短距离。
第1步:以源点(假设是s1)为开始点,求最短距离,如何求呢? 与这个源点相邻的点与源点的距离全部放在一个数组dist[]中,如果不可达,dist[]中为最大值,这里说一下,为什么要是一维数组,原因是默认的是从源点到这个一维数组下标的值,只需要目的点作为下标就可以,这时从源点到其他点的最短的“一”条路径有了,只要选出dist[]中最小的就行(得到最短路径的另一个端点假设是s2)。
第2步:这时要寻找源点(假设是s1)到另外点的次短距离,这个距离或者是dist[]里面的值,或者是从第1步中选择的那个最短距离 + 从找到点(假设是s2)出发到其他点的距离(其实这里也是一个更新操作,更新的是dist[]里面的值),如果最短距离 + 从这点(假设是s2)到其他点的距离,小于dist[]里面的值,就可以更新dist[]数组了,然后再从dist[]数组中选一个值最小的,也就是第“二”短路径(次短路径)。
第3步:寻找第“三”短路径,这时同上,第二短路径的端点(s3)更新与之相邻其他的点的dist[]数组里面的值。
第4步:重复上述过程n - 1次(n指的是节点个数),得出结果,其实把源点到所有点的最短路径求出来了,都填在了dist[]表中,要找源点到哪个点的最短路,就只需要查表了。
Dijkstra模版:
//dijkstra算法模版,狄杰斯特拉算法模板,单源最短路模板 //dijkstra算法模版 #include <stdio.h> #include <string.h> #define MaxN 1001 #define MaxInt 200000000 int map[MaxN][MaxN],dist[MaxN]; bool mark[MaxN]; int n,start,end; int main() { int i,j,min1,minj,temp; //****************输入********************** scanf("%d%d%d",&n,&start,&end); for (i=1;i<=n;i++) for (j=1;j<=n;j++) scanf("%d",&map[i][j]); //****************************************** //*****************初始化********************** for (i=1;i<=MaxN;i++) dist[i]=MaxInt; memset(mark,0,sizeof(mark)); dist[start]=0; //把起点并入集合,搜索时就可以从起点寻找到第一条最短的边了 //********************************************* for (i=1;i<=n-1;i++) { min1=MaxInt; for (j=1;j<=n;j++) //查找到原集合的最短的边 { if (!mark[j]&&dist[j]<min1) { min1=dist[j]; minj=j; } } mark[minj]=1; for (j=1;j<=n;j++) //每并入一个点都要对原来的边进行修正,保证任意时刻源点到目标点的距离都是最短的。 { if (!mark[j]&&map[minj][j]>0) { temp=dist[minj]+map[minj][j]; if (temp<dist[j]) dist[j]=temp; } } } //***********输出*************** printf("%d ",dist[end]); //****************************** return 0; }
Prim算法中寻找的是下一个与MST中任意顶点相距最近的顶点;
Dijkstra算法寻找的是下一个离给定顶点(单源)最近的顶点。
另外,当有两条具有同样的最小权值的边可供选择时,任选一条即可,所以构造的MST不是惟一的。
Prim算法和Dijkstra算法十分相似,惟一的区别是: Prim算法要寻找的是离已加入顶点距离最近的顶点;
Dijkstra算法是寻找离固定顶点距离最近的顶点。
所以Prim算法的时间复杂度分析与Dijkstra算法相同,都是 O(|V^2|)
【拓】:Kruskal算法:http://baike.baidu.com/link?url=MchMLaw4a3nLu3bWSoEUEak-DYbM8n0H27ANKE5-Gv_frudxAvGfsqdpNRqDtdB0
克鲁斯卡尔(Kruskal)算法(只与边相关)
算法描述:克鲁斯卡尔算法需要对图的边进行访问,所以克鲁斯卡尔算法的时间复杂度只和边又关系,可以证明其时间复杂度为O(eloge)。
算法过程:
1.将图各边按照权值进行排序
2.将图遍历一次,找出权值最小的边,(条件:此次找出的边不能和已加入最小生成树集合的边构成环),若符合条件,则加入最小生成树的集合中。
不符合条件则继续遍历图,寻找下一个最小权值的边。
3.递归重复步骤1,直到找出n-1条边为止(设图有n个结点,则最小生成树的边数应为n-1条),算法结束。得到的就是此图的最小生成树。
克鲁斯卡尔(Kruskal)算法因为只与边相关,则适合求稀疏图的最小生成树。而prime算法因为只与顶点有关,所以适合求稠密图的最小生成树。
代码:
#include<iostream> #include<cstring> #include<string> #include<cstdio> #include<algorithm> using namespace std; #define MAX 1000 int father[MAX], son[MAX]; int v, l; typedef struct Kruskal //存储边的信息 { int a; int b; int value; }; bool cmp(const Kruskal & a, const Kruskal & b) { return a.value < b.value; } int unionsearch(int x) //查找根结点+路径压缩 { return x == father[x] ? x : unionsearch(father[x]); } bool join(int x, int y) //合并 { int root1, root2; root1 = unionsearch(x); root2 = unionsearch(y); if(root1 == root2) //为环 return false; else if(son[root1] >= son[root2]) { father[root2] = root1; son[root1] += son[root2]; } else { father[root1] = root2; son[root2] += son[root1]; } return true; } int main() { int ncase, ltotal, sum, flag; Kruskal edge[MAX]; scanf("%d", &ncase); while(ncase--) { scanf("%d%d", &v, &l); ltotal = 0, sum = 0, flag = 0; for(int i = 1; i <= v; ++i) //初始化 { father[i] = i; son[i] = 1; } for(int i = 1; i <= l ; ++i) { scanf("%d%d%d", &edge[i].a, &edge[i].b, &edge[i].value); } sort(edge + 1, edge + 1 + l, cmp); //按权值由小到大排序 for(int i = 1; i <= l; ++i) { if(join(edge[i].a, edge[i].b)) { ltotal++; //边数加1 sum += edge[i].value; //记录权值之和 cout<<edge[i].a<<"->"<<edge[i].b<<endl; } if(ltotal == v - 1) //最小生成树条件:边数=顶点数-1 { flag = 1; break; } } if(flag) printf("%d ", sum); else printf("data error. "); } return 0; }