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

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

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

【最短路】常用的算法有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 1000
using 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 20008

int 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;  
}  



















  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值