【区别】最短路&最小生成树

来源:互联网 发布:单片机应用技术c语言版 编辑:程序博客网 时间:2024/05/22 18:23

一句话概括:最小生成树是计算从一节点到另一节点的最小边集;最短路是带权路径,计算权值最小。也就是说,最小生成树要经过每一个点,而最短路只需要能达到某两点,路径权值最小即可!

两个算法具有相当大的相似性,而且都用到了贪心思想,所以把他们放到一起。

【最短路】常用的算法有dijkstra,bellman-ford,floyd,而【最小生成树】则是prim和kruskal。下面是各个算法的模板。
【Dijkstra复杂度O(n^2)】

#include<stdio.h>  //最短路  #define maxsum 0x3fffffff    int map[101][101],dist[101],s[101];    void Dijkstra(int n,int x)  //n,1,递推实现  {        int mindis,u,i,j;        for(i=1;i<=n;i++)        {            dist[i]=map[i][x];//map[x][i] is OK,dist表示i到原点的最小距离!          s[i]=0; //printf("%d/n",dist[i]);      }        s[x]=1;// x=1      for(i=1;i<=n;i++)        {            mindis=maxsum;            u=-1;           // 找出当前未使用的点j的dist[j]最小值          for(j=1;j<=n;j++)          if(s[j]==0 && dist[j]<mindis)//------------(2)          {                u=j;  //保存当前邻接点中最小的              mindis=dist[j];            }            s[u]=1;//u点已存入s集合,最小的!所以放外面           //更新dist          for(j=1;j<=n;j++)  //-------------------(3)            if(s[j]==0)                if( dist[u]+map[u][j]<dist[j] && map[u][j]<maxsum) //从u点发散出去寻找,map[u][j]<maxsum存在权值 ,map[j][u] is OK               dist[j]=dist[u]+map[u][j];        }    }    int main()    {        int n,m,a,b,c,i,j;        while(scanf("%d%d",&n,&m)!=EOF)        {            if(n==0&&m==0) break;            for(i=1;i<=n;i++)              for(j=1;j<=n;j++)              map[i][j]=maxsum;//初始化邻接矩阵          for(i=1;i<=m;i++)            {               scanf("%d%d%d",&a,&b,&c);                map[a][b]=map[b][a]=c;//构造邻接矩阵,对称的!无向!          }            Dijkstra(n,1);            printf("%d/n",dist[n]);      }        return 0;    }     

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
【最小生成树的prim模板:复杂度O(n^2)】
#include<iostream>//最小生成树#define INF 0x1f1f1f1f#define M 1000using namespace std;double dis[M],map[M][M];bool flag[M];int prim(int s,int n)                        //s为起点,n为点的个数{    int i,j,k,temp,md,total=0;    for(i=1;i<=n;i++)        dis[i]=map[s][i];                    //与最短路不同,而是将dis置为map[s][i]    memset(flag,false,sizeof(flag));    flag[s]=true;                            //将起点加入集合    for(i=1;i<n;i++){                        //依旧进行n-1次迭代,每次找到不在集合的最小边(n个点有n-1条边)!!!!!!        md=INF;    for(j=1;j<=n;j++){        if(!flag[j]&&dis[j]<md){            md=dis[j];            temp=j;        }    }    flag[temp]=true;                      //将找到的最小边的点加入集合    total+=md;                            //并将这个边的权值加到total中    for(j=1;j<=n;j++)                     //松弛操作,注意与最短路不同    if(!flag[j]&&dis[j]>map[temp][j])        dis[j]=map[temp][j];       }    return total;}

【Kruskal最小生成树模板  复杂度O(E*logE)】

typedef struct edge{    int a;    int b;    int value;}edge;edge edges[earraysize];int final[narraysize];            //存储父节点 中括号里面是儿子,外面是父亲int nodecount[narraysize];        //存储该节点孩子结点的个数 bool cmp(edge a,edge b){     return a.value<b.value;}int findp(int x)    //寻找父亲{    while(x!=fa[x])        x=fa[x];    return x;}bool Union(int x,int y)          //合并 {    int rootx=findp(x);          /*为什么要找父亲?因为要判是否有回路,假如父亲相同,而x跟y连通,那么就形成了回路*/    int rooty=findp(y);    if(rootx==rooty)        return false;    else if(nodecount[rootx]<=nodecount[rooty])      //优化,把深度小的子树加到深度大的子树,减少树的高度    {        final[rootx]=rooty;                         /*其实不优化也可以直接final[rootx]=rooty或者final[rooty]=rootx也ok */        nodecount[rooty]+=nodecount[rootx];    }    else    {        final[rooty]=rootx;        nodecount[rootx]+=nodecount[rooty];    }    return true;}int main (){    //freopen("a.txt","r",stdin);    int num=0;    int n,m;    int i,j;    while ( scanf ( "%d%d", &n, &m ) != EOF )    {        num=0;              //记录生成树中的边的数目        for(i=1;i<=m;i++)        {            scanf("%d%d%d",&edges[i].a,&edges[i].b,&edges[i].value);        }        for(i=1;i<=n;i++)      //初始化                        {            final[i]=i;            nodecount[i]=1;        }        sort(edges+1,edges+m+1,cmp);   //排序                                     for(i=1;i<=m;i++)              //遍历所有的边         {            if(Union(edges[i].a,edges[i].b))         //合并             {                num++;            }            if(num==n-1)               //找到了最小生成树                 break;        }    }    return 0;}



*****************************************************************************************************************************************************************************************

*****************************************************************************************************************************************************************************************
下面是最短路的bellmen-ford算法,与dijkstra不同,bellman-ford可以运用于有负权值的图,不过复杂度很高,O(VE )... 慎用~(可以用SPFA,它bwllman-ford的扩展)
Bellman-ford算法同样是对每条边进行N-1次松弛,当有权值为负时,对所有边进行N-1次松弛,如果dis还能更新,说明有负环。

ps:名词解释【负环】 在一个图里每条边都有一个权值(有正有负)
如果存在一个环(从某个点出发又回到自己的路径),而且这个环上所有权值之和是负数,那这就是一个负权环,也叫负权回路
存在负权回路的图是不能求两点间最短路的,因为只要在负权回路上不断兜圈子,所得的最短路长度可以任意小。

Bellman-ford原理:

1.如果最短路存在,则每个顶点最多经过一次,因此不超过n-1条边;

2.长度为k的路由长度为k-1的路加一条边得到;

3.由最优性原理,只需依次考虑长度为1,2,…,k-1的最短路。


Bellman-ford模板:

#include<stdio.h>//最短路#include<string.h>#define INF 0x1f1f1f1f#define MAX 102#define MAXM 20008int dist[MAX];struct Edge{                                                   //边结构体定义     int u, v, w;    Edge(){}    Edge(int a, int b, int c):u(a), v(b), w(c){}}edge[MAXM];int bellman_ford(int n, int m, int s)                           //n个点、m条边、s为起点 {    memset(dist, 0x1f, sizeof(dist));                          //初始化距离很大     dist[s] = 0;    int i, j, u, v, f;    for (i = 1; i < n; ++i)                                   //迭代 n - 1 次,对每条边进行n-1次松弛    {        f = 0;        for (j = 0; j < m; ++j)        {            u = edge[j].u;            v = edge[j].v;            if (dist[v] > dist[u] + edge[j].w)               // 松弛操作             {                dist[v] = dist[u] + edge[j].w;                f = 1;                }         }         if (!f) return 1;                                     //如果其中一次迭代没改变,停止     }     for(j = 0; j < m; ++j)                                   //再进行一次迭代      {         u = edge[j].u;         v = edge[j].v;         if (dist[v] > dist[u] + edge[j].w)                   //若还能松弛, 则存在负环          return -1;                                        //存在负环返回 -1      }   return 1;                                           //没有负环返回 1 }


算法结束后dist数组已经是最短路径。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

【SPFA模板: 期望的时间复杂度O(KE), 其中K为所有顶点进队的平均次数,可以证明K一般小于等于2。】

#include<iostream>//最短路  #include<string.h>  #include<cstdio>  #include <vector>  #include <queue>  using namespace std;  #define N 50001int   INF = 0x7fffffff;  int n,m;  typedef struct edge  {         int to;         int w;  }edge,temp;  vector<edge> adjmap[N]; //vector实现邻接表  int d[N];  bool vis[N];          //记录顶点是否在队列中,SPFA算法可以入队列多次  int cnt[N];             //记录顶点入队列次数  void SPFA()  {           queue<int>       myqueue;           int i;           for(i=1;i<=n;++i)                      d[i] = INF;        //将除源点以外的其余点的距离设置为无穷大           memset(vis,0,sizeof(vis));           memset(cnt,0,sizeof(cnt));           d[1]=0;              //源点的距离为0           vis[1] = true;           cnt[1]++;            //源点的入队列次数增加           myqueue.push(1);           int topint;           while(!myqueue.empty())           {                   topint = myqueue.front();                   myqueue.pop();                   vis[topint] = false;                   for(i=0;i<adjmap[topint].size();++i)                   {                           int to = adjmap[topint][i].to;                           if(d[topint]<INF && d[to]>d[topint]+ adjmap[topint][i].w)                           {                                    d[to] = d[topint]+ adjmap[topint][i].w;                                    if(!vis[to])                                    {                                            vis[to] = true;                                            cnt[to]++;                                            if(cnt[to]>=n)   //当一个点入队的次数>=n时就证明出现了负环。                                              return ;                                            myqueue.push(to);                                    }                       }                }                     } printf("%d/n",d[n]);    }  int main()  {          //freopen("a.txt","r",stdin);          scanf("%d%d",&n,&m);          int i;          int s,e,w;          edge temp;          for(i=1;i<n+1;++i)       //此处特别注意对邻接表清空              adjmap[i].clear();          for(i=0;i<m;++i)         //双向          {                  cin>>s>>e>>w;                  temp.to = e;                  temp.w = w;                  adjmap[s].push_back(temp);                  temp.to = s;                  adjmap[e].push_back(temp);          }           SPFA();           return 0;  }  



















原创粉丝点击