Dijkstra和Prim算法的区别

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\n",dist[end]);
//******************************
    return 0;
}
View Code

 

 

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\n", sum);
        else printf("data error.\n");
    }
    return 0;
}
View Code

 

 

 

 

posted @ 2013-12-08 21:06  寻找&星空の孩子  阅读(8486)  评论(0编辑  收藏  举报