无向图最小生成树的Prim算法实现

无向图最小生成树的Prim算法实现

前言

本文讲解最小生成树的定义及实现原理,并根据最小生成树原理介绍贪心算法,以及讲解在贪心算法基础上延伸出来的Prim算法的思想及代码实现。

零、无向图的约定

为了更好理解最小生成树,假设采用的图是连通图,并且图中所有边的权重不一样。

一、生成树定义

图的生成树是它的一棵含有其所有顶点的无环连通子图,一幅图的生成树可以有很多。

二、最小生成树定义

最小生成树是图的所有生成树中的一棵权值(树中所有边的权重之和)最小的生成树

三、生成树性质

  1. 用一条边接树中的任意两个顶点都会产生一个新的环
    在这里插入图片描述
  2. 从树中删除任意一条边,将会得到两棵独立的树
    在这里插入图片描述

四、最小生成树切分定理

切分定理用于找出一幅连通图中的最小生成树,任何最小生成树的方法都是基于切分定理。

切分定理包含两个概念:

  1. 切分:将图的所有顶点按照某些规则分为两个非空且没有交集的集合。
  2. 横切边:连接两个属于不同集合的顶点的边称之为横切边。
  3. 切分定理就是在一副加权图中,给定任意的切分,它的横切边中的权重最小者必然属于图中的最小生成树。

因此切分定理包括两个关键点:1. 如何切分? 2.找到权重最小的横切边

五、最小生成树贪心算法

根据切分定理找到图中的最小生成树就是贪心算法,它使用切分定理找到最小生成树的一条边,不断的重复直到找到最小生成树的所有边。如果图有V个顶点,那么需要找到V-1条边,就可以表示该图的最小生成树。

贪心算法只是一种思想,在这里可以理解为它用来更好的解释切分定理而已。通过找到每个顶点的局部最优解,然后逐步贪心寻找,最终由局部最优推出全局最优解,即最小生成树。

六、Prim算法

Prim算法始终将图中的顶点切分成两个集合,最小生成树顶点和非最小生成树顶点,通过不断的重复做某些操作,可以逐渐将非最小生成树中的顶点加入到最小生成树中,直到所有的顶点都加入到最小生成树中。

1.切分定理的其中一个关键点是如何切分?Prim算法给出切分方法如下:
Prim算法首先任意选择一个顶点P作为一个集合(也是最小生成树的第一个顶点),其余顶点作为另一个集合;然后根据切分定理,获得与顶点P形成边的另一个顶点M(与顶点P相连的所以顶点中,与顶点M组成的边的权值最小);然后把M顶点加入P顶点的集合,剩余顶点成为另一个集合,再根据切分定理进行找最小生成树的其它顶点,如此往复,直至最后所有顶点都在一个集合中,这时最小生成树完成。

2.Prim算法代码实现

/**
 * 基于Prim算法实现加权无向图中最小生成树的查找。
 */
public class PrimMST {
    private Edge[] edgeTo;//索引代表顶点,值表示当前顶点和最小生成树之间的最短边
    private double[] distTo;//索引代表顶点,值表示当前顶点和最小生成树之间的最短边的权重
    private boolean[] marked;//索引代表顶点,如果当前顶点已经在最小生成树中,则值为true,否则为false
    private IndexMinPriorityQueue<Double> pq;//存放树中顶点与非树中顶点之间的有效横切边(索引代表顶点,值代表权重)

    //根据一副加权无向图,创建最小生成树计算对象;
    public PrimMST(EdgeWeightedGraph G)
    {
        this.edgeTo=new Edge[G.V()];
        this.distTo=new double[G.V()];
        for (int i = 0; i < distTo.length; i++) {
            distTo[i]=Double.POSITIVE_INFINITY;
        }
        this.marked=new boolean[G.V()];
        this.pq=new IndexMinPriorityQueue<Double>(G.V());

        //初始将顶点0作为最小生成树的结点
        distTo[0]=0.0;
        pq.insert(0,0.0);
        //遍历索引最小优先队列,将最小权重对应的顶点加入最小生成树中
        while (!pq.isEmpty())
        {
            visit(G,pq.delMin());
        }
    }

    //将顶点v添加到最小生成树中,并且更新数据
    private void visit(EdgeWeightedGraph G, int v)
    {
        //将顶点v添加到最小生成树中
        marked[v]=true;
        //更新数据
        //1.查找当前顶点v邻接表中所有顶点,找到权重最小的边
        for (Edge edge : G.adj(v)) {
            //查找邻接表中的顶点是否已近在最小生成树中,如果在,则不再处理该边
            int w = edge.other(v);
            if (marked[w])
                continue;
            //判断边的权重是否小于 顶点w与最小生成树中顶点相连的边
            if (edge.weight()<distTo[w])
            {
                //如果已经存在顶点与v顶点相连,则需要比较它的权重是否小于顶点w与v的权重,如果是,则替换顶点w的权重与边
                distTo[w]=edge.weight();
                edgeTo[w]=edge;
                //如果顶点w与顶点v之间已经存在权重值,则修改权重值为当前边的权重值,否则,插入当前边的权重值
                if (pq.contains(w))
                    pq.changeItem(w,edge.weight());
                else
                    pq.insert(w,edge.weight());
            }
        }
    }

    //获取最小生成树的所有边
    public Queue<Edge> edges()
    {
        Queue<Edge> edges = new Queue<>();
        for (int i = 0; i < edgeTo.length; i++) {
            if (edgeTo[i]!=null)
                edges.enqueue(edgeTo[i]);
        }
        return edges;
    }
}
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mekeater

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值