最小生成树(MST):Prim / Kruskal

原创 2014年11月24日 09:56:29

* 假设T1集合是已加入最小生成树中的点,T2集合是剩下的待加入T2的点 
* 我们要做的是把T2集合中离T1最近的那个点,加入T1 
* 所以我们需要知道: 
* 集合T2内各顶点到集合T1的距离 
*  
* 为此,我们用两个数组: 
* cost[i]:用来表示T2中点i到T1的距离; 
* pre[i]:用来表示T2中点i和T1中哪个点最近(为了输出加入时的路径,不输出路径则不需要此数组) 
* visited[i],代表i加入了T1集合  
*  
* NO.1 
* 选取起始点v,将v加入T1,并对剩余各点进行距离初始化: initCost() 
* NO.2   
* 选择一个在T2集合中,距离T1集合最近的点k: findNext() 
* NO.3 
* 将k加入到集合T1中:visited[k] = 1
* NO.4   
* 更新剩余T2中的各点到T1集合的距离 
* (如果因为新加入的点k而使得T2中的点到T1的距离缩小,将此点的前导点改为k) 
*if     edge[k][i]<cost[i] 
*then   cost[i]=edge[k][i];  pre[i]=k; 

* 这就完成了一次操作,加入一条边。 
* 由于MST一定是n-1条边,进行n-1次后就完成了MST。

#include<stdio.h>
#include<string.h>

#define INF 0xfffffff						// infinity
#define MAXSIZE 20

int num, n;                                 // num: line; n: vertex
int edge[MAXSIZE][MAXSIZE];                 // 邻接矩阵
int cost[MAXSIZE], pre[MAXSIZE], visited[MAXSIZE];     // 距离标记;前导点标记
                                            // 所有数组从1开始

void Prim(int u);

int main()
{
    int i, j;
    int row, col, value;
    
    memset(edge, 0, sizeof(edge));			//void * memset (void * p,int c,size_t n);
    
    scanf("%d%d", &n, &num);
    
    for(i = 0; i < num; i++)				// 构造邻接矩阵
    {
        scanf("%d%d%d", &row, &col, &value);
        edge[row][col] = edge[col][row] = value;
    }
    
    for(i = 1; i <= n; i++)				    //将邻接矩阵对角线赋为0
        for(j = 1; j <= n; j++)				//原来就为0的赋为INF
            if(i == j)
                edge[i][j] = 0;
            else if( edge[i][j] == 0 )
                edge[i][j] = INF;
            
            Prim(1);				    	//Prim算法,不妨从顶点1开始
            
            return 0;
}

//NO.1 对距离进行初始化
void initCost(int u)
{
    int i;
    for(i = 1; i <= n; i++)
    {
        cost[i] = edge[u][i];
        pre[i] = u;
    }
    cost[u] = 0;
    visited[u] = 1;
}

//NO.2 找到不在T1中,边的权值最小的那个点
int findNext()
{
    int i;
    int mincost = INF, v = -1;
    for(i = 1; i <= n; i++)
    {
        if(visited[i] != 1 && cost[i] < mincost)
        {
            mincost = cost[i];
            v = i;
        }
    }
    return v;
}

//NO.4 加入后,对距离进行更新
void updateCost(int v)
{
    int i;
    for(i = 1; i <= n; i++)
    {
        if(visited[i] != 1 && edge[v][i] < cost[i])
        {
            cost[i] = edge[v][i];
            pre[i] = v;
        }
    }
}

//在dijkstra算法中逆向打印点v到源点的路径
//在prim算法中不需要
void print_path(int v) //打印最短路的路径(反向)
{
    while(v != pre[v]) //前驱
    {    
        printf("%d<--", v);
        v = pre[v];
    }
    printf("%d\n", v);
}

void Prim(int u)
{
    int i, j;
    int v = -1;
    int weight = 0;
    
    initCost(u);                            //NO.1 对距离进行初始化
    
    for(i = 1; i < n; i++)					//循环n-1次,加入n-1个顶点
    {
        v = findNext();             		//NO.2 找到不在T1中,边的权值最小的那个点
        if(v != -1)						    //如果找到那个点v
        {
            printf("%d--(%d)-->%d\n", 
                pre[v], cost[v], v);     //打印加入的流程
            visited[v] = 1;				    //NO.3 将v点加入T1
             weight += cost[v];
            
            updateCost(v);					//NO.4 对距离进行更新
        } 
    }
    printf("The sum-weight of the MST is %d\n", weight);

    //just for fun
    print_path(7);                          //比如打印点7到源点的逆向路径玩玩儿=.=
}





相对于dijkstra而言,Prim最小生成树和Dijkstra最短路径的微小的差别为:“权值最低”的定义不同!

Prim的“权值最低”是相对于U中的任意一点而言的,也就是把U中的点看成一个整体,每次寻找V - U中跟U的距离最小(也就是跟U中任意一点的距离最小)的一点加入U;而Dijkstra的“权值最低”是相对于v0而言的,也就是每次寻找V-U中跟v0的距离最小的一点加入U

因此对于最短路径,只要将上面MST的路径距离更新的代码改成以下部分(多加了lowcost[v]):

if(visited[i] != -1 && edge[v][i] + cost[v] < cost[i])
{
    cost[i] = edge[v][i] + cost[v];
    pre[i] = v;
}

就OK了!


相对于Prim的复杂度O(n*n),用Kruskal是个更简单也更好的选择,它的复杂度完全由排序决定。如果是用快排,则Kruskal的复杂度可以简化为O(n*logn)。

//Kruskal
#include<stdio.h>
#include<stdlib.h>

#define N 1010

typedef struct node
{
    int begin, end;                                 //这里的begin和end其实并没有“有向图”的概念,还是代表无向图
    int value;
}Node;

Node edge[N];                                       //存储边的信息
int root[N];                                        //存储每个点所在的集合的根

int rising(const void *a, const void *b)
{
    return ((Node *)a)->value - ((Node *)b)->value; //注意强制转型的优先级没有->高
}

int find(int point)                             //并查集的“查”
{
    if(root[point] == -1)                       //如果根结点信息为-1,则自己就是自己所在集合的根
        return point;                                   //所以返回自己
    return root[point] = find(root[point]);     //否则压缩路径,并返回point所在集合的根=.=话说这一句信息量好大
}

//话说刚刚的return一次性将压缩路径和返回集合所在的根都解决了,相当于下面这个函数

/*
int pre[1000 ];
int find(int x)                  //查找根节点
{ 
    int r=x;
    while ( pre[r ] != r )       //找到根节点 r
          r=pre[r ];
 
    int i=x , j ;
    while( i != r )              //路径压缩
    {
         j = pre[ i ];           // 在改变上级之前用临时变量  j 记录下他的值 
         pre[ i ]= r ;           //把上级改为根节点
         i=j;
    }
    return r ;
}
*/

void join(int x, int y)                         //并查集的“并”
{
    if( find(x) != find(y) )         //如果两个点xy所在集合的根不一样,
        root[ find(x) ] = find(y);   //则将一个点合并到另一个集合(实际操作是置两个点的根为同一个根)
}

int main()
{
    int n, m, i, sum = 0;

    for(i=0; i<N; i++)                          //刚开始将所有的点的根初始化为-1
        root[i] = -1;
    scanf("%d%d", &n, &m);
    for(i=0; i<m; i++)
        scanf("%d%d%d", &edge[i].begin, &edge[i].end, &edge[i].value);

    qsort(edge, m, sizeof(Node), rising);       //按照边的权值快排nlogn

    for(i=0; i<m; i++)
    {
        if( find(edge[i].begin) != find(edge[i].end) )//如果两个点不在一个集合,意味着连接两点不会成环
        {
            join(edge[i].begin, edge[i].end);           //将一个点并到另一个点所在的集合
            sum += edge[i].value;
        }
    }
    printf("%d", sum);
    return 0;
}



版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

最小生成树(MST)----普里姆(Prim)算法与克鲁斯卡尔(Kruskal)算法

1、概念:给定一个带权的无向连通图,如何选取一棵生成树,使树上所有边上权的总和为最小,这叫最小生成树. 2、应用:例如:要在n个城市之间铺设光缆,主要目标是要使这 n 个城市的任意两个之间都可以通信...

最小生成树(MST)—prim和kruskal算法

最小生成树(MST:minimum-cost spanning tree) 也称最小支撑树,任何只由G的边构成,并包含G的所有顶点的树称为G的生成树(G连通).加权无向图G的生成树的代价是该生成树的...

Hrbust 1123 MST(最小生成树,Kruskal算法|Prim算法)

首先我们需要什么是最小生成树举个例子: 假如有10个村庄,你需要走遍这10个村庄,不漏掉任何一个村庄,如何走最省力(路程最短),就是最小生成树了。Description既然大家都不愿意做水题,元帅也...

数据结构基础6.3:最小生成树MST(Prim、Kruskal)

一.最小生成树: 连通图的一次遍历所经过的边的集合及图中所有顶点的集合就构成了该图的一棵生成树,对连通图的不同遍历,就可以得到不同的生成树。 如果无向连通图就是一个网,那么,它的所有生成树中必有一棵边...

Prim算法求最小生成树MST以及和kruskal算法的对比

1.解析Prim算法和Dijkstra算法非常类似,他们的伪码几乎相近,只是他们优先队列所排序的键值不同而已。Prim算法的键值为节点与集合S中顶点间的最轻边的权重,而在Dijkstra算法中,键值为...

Prim和Kruskal算法求最小生成树

  • 2014年03月09日 18:55
  • 8KB
  • 下载

最小生成树(MST)——Kruskal算法

最小生成树Kruskal算法是在处理一个森林——树的集合。开始时将图的每个顶点看做一棵树(集合),然后采用贪婪策略,每次从所有边中依次选出(Find)权值最小的边,当改边的两个端点不在同一集合时,则将...

prim和kruskal算法求最小生成树

  • 2008年11月16日 07:55
  • 239KB
  • 下载

图结构之最小生成树(MST)——Prims(普里姆)算法、Kruskal(克鲁斯卡尔)算法

最小生成树的两个经典算法——Prims(普里姆)算法、Kruskal(克鲁斯卡尔)算法,笔者自己分别理解为:割边、加边...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:最小生成树(MST):Prim / Kruskal
举报原因:
原因补充:

(最多只允许输入30个字)