普里姆算法(Prim算法)

普里姆算法(Prim算法)

简介

普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (graph theory)),且其所有边的权值之和亦为最小。该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克(英语:Vojtěch Jarník)发现;并在1957年由美国计算机科学家罗伯特·普里姆(英语:Robert C. Prim)独立发现;1959年,艾兹格·迪科斯彻再次发现了该算法。因此,在某些场合,普里姆算法又被称为DJP算法、亚尔尼克算法或普里姆-亚尔尼克算法。

算法描述
  1. 输入:一个加权连通图,其中顶点集合为V,边集合为E;
  2. 初始化: V n e w V_{new} Vnew = {x},其中x为集合V中的任一节点(起始点), E n e w E_{new} Enew = {},为空;
  3. 重复下列操作,直到 V n e w V_{new} Vnew= V:
    a.在集合E中选取权值最小的边 < u, v>,其中u为集合 V n e w V_{new} Vnew中的元素,而v不在 V n e w V_{new} Vnew集合当中,并且v∈V(如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一);
    b.将v加入集合 V n e w V_{new} Vnew中,将< u, v>边加入集合 E n e w E_{new} Enew中;
  4. 输出:使用集合 V n e w V_{new} Vnew E n e w E_{new} Enew来描述所得到的最小生成树。
图示

prim

简略证明

反证法:假设prim生成的不是最小生成树

  1. 设prim生成的树为G0
  2. 假设存在 G m i n G_{min} Gmin使得cost( G m i n G_{min} Gmin)< cost( G 0 G_{0} G0) 则在 G m i n G_{min} Gmin中存在< u,v>不属于 G 0 G_{0} G0
  3. 将< u,v>加入 G 0 G_{0} G0中可得一个环,且< u,v>不是该环的最长边(这是因为< u,v>∈ G m i n G_{min} Gmin)
  4. 这与prim每次生成最短边矛盾
  5. 故假设不成立,命题得证
基本操作函数

prim(n):建立最小生成树,并输出其权值和

代码模板(含详细注释)

这里用 http://poj.org/problem?id=1258 作为模板题

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 110   //最多顶点数
#define MAX 0x3f3f3f3f  //模拟无穷大
int map[N][N];  //存储各顶点间的权值
int flag[N];    //标记是否已纳入树
int dis[N];     //已纳入点和其余各点的最小权值
int prim(int n)        //普利姆函数
{
    int i, j;
    int now;    //记录新纳入的点
    int min;    //记录新纳入的点到其余已纳入的点的最小权值
    int sum = 0;    //最小生成树权值和
    memset(dis, MAX, sizeof(dis));  //初始化dis数组为无穷大
    memset(flag, 0, sizeof(flag));  //初始化flag数组,0表示此点未被纳入
    /*这里随机选取了1号点为初始时被纳入的顶点*/
    for(i = 1; i <= n; i++)
        dis[i] = map[1][i];     //与1号点与其他点的权值存入dis数组
    dis[1] = 0;         //一号点到其本身的权值为0
    flag[1] = 1;        //标记为已纳入

    for(i = 1; i < n; i++){         //除去初始时随机纳入的点还有n-1个点应被纳入
        now = min = MAX;            //初始为无穷大表示两点间无通路
        for(j = 1; j <= n; j++){    //遍历
            if(flag[j] == 0){
                if(dis[j] < min){   //寻找与已纳入各点权值最小的点
                    now = j;
                    min = dis[j];
                }
            }
        }
        if(now == MAX)      //若now等于max,则证明所有与初始时纳入的点连通的点已全被纳入
            break;
        sum += min;     //将找到的点纳入并标记
        flag[now] = 1;
        for(j = 1; j <= n; j++){
            /*
                遍历比较之前纳入点到未纳入点的权值的最小值
                与刚纳入点到未纳入点的权值
                并用dis[j]存储新的最小值
            */
            if(flag[j] == 0)
                if(dis[j] > map[now][j])
                    dis[j] = map[now][j];
        }
    }
    if(i == n)      //若i等于n则证明已经建立最小生成树
        return sum;
    else
        return -1;
}
int main(void)
{
    int i, j, k;
    int n;      //顶点数
    while(scanf("%d", &n) != EOF){
        for(i = 1; i <= n; i++)
            for(j = 1; j <= n; j++)
                scanf("%d", &map[i][j]);  //输入i和j之间的距离
        if((k = prim(n)) != -1) //调用prim函数
            printf("%d\n", k);
        else
            printf("烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫\n");
    }
    return 0;
}


模板题样例图解
prim2

运行示例

运行示例

  • 32
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
普里姆(Prim)算法和克鲁斯卡尔(Kruskal)算法都是用于解决最小生成树问题的算法最小生成树问题是指在一个无向连通图中,找到一棵生成树,使得树上所有边的权值之和最小。生成树是指一个无向图的生成子图,它是一棵树,且包含图中所有顶点。 下面我们分别介绍普里姆算法和克鲁斯卡尔算法: 1. 普里姆算法 普里姆算法是一种贪心算法,它从一个任意点开始,逐步扩展生成树,每次选择当前生成树到未加入的点中距离最近的点,并将其加入生成树。 具体实现步骤如下: - 随机选择一个起始点,将其加入生成树。 - 在生成树中的所有节点中,找到到未加入生成树的节点中距离最小的节点,将其加入生成树。 - 重复以上步骤,直到生成树包含了所有节点。 2. 克鲁斯卡尔算法 克鲁斯卡尔算法也是一种贪心算法,它从边集合中选择边,逐步扩展生成树,每次选择当前边集合中权值最小的边,并将其加入生成树。 具体实现步骤如下: - 将所有边按照权值从小到大排序。 - 从权值最小的边开始,逐个加入生成树,如果加入当前边会形成环,则不加入该边。 - 重复以上步骤,直到生成树包含了所有节点。 两种算法的时间复杂度都是O(ElogE),其中E为边数。普里姆算法在处理稠密图时效率更高,而克鲁斯卡尔算法在处理稀疏图时效率更高。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值