Prim算法实现、最小生成树(加权无向图)

最小生成树

加权图,我们发现它的边关联了一个权重,那么我们就可以根据这个权重解决最小成本问题,但如何才能找到最小成本对应的顶点和边呢?最小生成树相关算法可以解决。

1. 最小生成树定义以及相关约定

定义:

图的生成树是它的一棵含有其所有顶点无环连通子图,一副加权无向图的最小生成树它的一棵权值(树中所有边的权重之和)最小的生成树

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XvQEp5fO-1630077611303)(images/image70.png)]

约定:

  1. 只考虑连通图。最小生成树的定义说明它只能存在于连通图中,如果图不是连通的,那么分别计算每个连通图子图的最小生成树,合并到一起称为最小生成森林
  2. 所有边的权重都各不相同。如果不同的边权重可以相同,那么一副图的最小生成树就可能不唯一了,虽然我们的算法可以处理这种情况,但为了好理解,我们约定所有边的权重都各不相同。

2.最小生成树原理

2.1 树的性质

  1. 用一条边接树中的任意两个顶点都会产生一个新的环;

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kLW7XFC7-1630077611307)(images/image72.png)]

  2. 从树中删除任意一条边,将会得到两棵独立的树;

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iKSlHV3D-1630077611310)(images/image73.png)]

2.2 切分定理

要从一副连通图中找出该图的最小生成树,需要通过切分定理完成。

切分:

将图的所有顶点按照某些规则分为两个非空且没有交集的结合。

横切边:

连接两个属于不同集合的顶点的边称之为横切边

例如我们将图中的顶点切分为两个集合,灰色顶点属于一个集合,白色顶点属于另外一个集合,那么效果如下:黑色加粗的为横切边

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3J09nh94-1630077611315)(images/image74.png)]

切分定理:

在一副加权图中,给定任意的切分,它的横切边中的权重最小者必然属于图中的最小生成树。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WzOy7ibH-1630077611316)(images/image75.png)]

**注意:**一次切分产生的多个横切边中,权重最小的边不一定是所有横切边中唯一属于图的最小生成树的边。

3. 贪心算法

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-843tfcWD-1630077611317)(images/image76.png)]

计算图的最小生成树的算法有很多种,但这些算法都可以看做是贪心算法的一种特殊情况,这些算法的不同之处在于保存切分和判定权重最小的横切边的方式。

4. Prim算法

我们学习第一种计算最小生成树的方法叫Prim算法,它的每一步都会为一棵生成中的树添加一条边。一开始这棵树只有一个顶点,然后会向它添加V-1条边,每次总是将下一条连接树中的顶点与不在树中的顶点且权重最小的边加入到树中。

Prim算法的切分规则:

把最小生成树中的顶点看做是一个集合,把不在最小生成树中的顶点看做是另外一个集合。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YxDqZAPD-1630077611317)(images/image77.png)]

4.1 Prim算法API设计

类名PrimMST
构造方法PrimMST(EdgeWeightedGraph G):根据一副加权无向图,创建最小生成树计算对象;
成员方法1.private void visit(EdgeWeightedGraph G, int v):将顶点v添加到最小生成树中,并且更新数据
2.public Queue edges():获取最小生成树的所有边
成员变量1.private Edge[] edgeTo: 索引代表顶点,值表示当前顶点和最小生成树之间的最短边
2.private double[] distTo: 索引代表顶点,值表示当前顶点和最小生成树之间的最短边的权重
3.private boolean[] marked:索引代表顶点,如果当前顶点已经在树中,则值为true,否则为false
4.private IndexMinPriorityQueue pq:存放树中顶点与非树中顶点之间的有效横切边

4.2 Prim算法的实现原理

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

我们在设计API的时候,使用最小索引优先队列存放树中顶点与非树中顶点的有效横切边,那么它是如何表示的呢?我们可以让最小索引优先队列的索引值表示图的顶点,让最小索引优先队列中的值表示从其他某个顶点到当前顶点的边权重。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F3NOv3SO-1630077611318)(images/image81.png)]

初始化状态,先默认0是最小生成树中的唯一顶点,其他的顶点都不在最小生成树中,此时横切边就是顶点0的邻接表中0-2,0-4,0-6,0-7这四条边,我们只需要将索引优先队列的2、4、6、7索引处分别存储这些边的权重值就可以表示了。

现在只需要从这四条横切边中找出权重最小的边,然后把对应的顶点加进来即可。所以找到0-7这条横切边的权重最小,因此把0-7这条边添加进来,此时0和7属于最小生成树的顶点,其他的不属于,现在顶点7的邻接表中的边也成为了横切边,这时需要做两个操作:

  1. 0-7这条边已经不是横切边了,需要让它失效:

    只需要调用最小索引优先队列的delMin()方法即可完成;

  2. 2和4顶点各有两条连接指向最小生成树,需要只保留一条:

    4-7的权重小于0-4的权重,所以保留4-7,调用索引优先队列的change(4,0.37)即可,

    0-2的权重小于2-7的权重,所以保留0-2,不需要做额外操作。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j4zXRHCF-1630077611319)(images/image82.png)]

我们不断重复上面的动作,就可以把所有的顶点添加到最小生成树中。

4.3 代码

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

    // 根据一副加权无向图,创建最小生成树计算对象
    public PrimMST(EdgeWeightedGraph G) {
        // 创建一个和图的顶点数一样大小的Edge数组,表示边
        this.edgeTo = new Edge[G.V()];
        // 创建一个和图的顶点数一样大小的double数组,表示权重,并且初始化数组中的内容为无穷大,无穷大即表示不存在这样的边
        this.distTo = new double[G.V()];
        for (int i = 0; i < distTo.length; i++) {
            distTo[i] = Double.POSITIVE_INFINITY;
        }
        // 创建一个和图的顶点数一样大小的boolean数组,表示当前顶点是否已经在树中
        this.marked = new boolean[G.V()];
        // 创建一个和图的顶点数一样大小的索引优先队列,存储有效横切边
        this.pq = new IndexMinPriorityQueue<>(G.V());

        // 默认让顶点0进入树中(也就是做根节点),所以让其与最小生成树之间的最短边的权重设置为0.0
        marked[0] = true;
        distTo[0] = 0.0;
        // 使用顶点0和权重0.0初始化有效横切边队列pq
        pq.insert(0, 0.0);
        // 遍历有效边队列
        while (!pq.isEmpty()) {
            // 找到权重最小的横切边对应的顶点,加入到最小生成树中
            visit(G, pq.delMin());
        }
    }

    // 将顶点v添加到最小生成树中,并且更新数据
    private void visit(EdgeWeightedGraph G, int v) {
        // 把顶点v加入树中
        marked[v] = true;
        // 遍历顶点v的连接表,得到每一条边Edge e
        for (Edge e : G.adj(v)) {
            // 得到与v相连的边的另一个顶点w
            int w = e.other(v);
            /*
                检测w是否已经在树中
                  在,则进行下一次循环,寻找与顶点v相连的不在树中的顶点
                  不在,则判断此边e的权重是否<此顶点w目前到最小生成树的权重
                     是:修改此顶点w到最小生成树的权重为此边的权重
                         修改此顶点w到最小生成树的边为此条边
                        判断pq中的有效横切边是否存在顶点w,存在则进行更新,不存在则进行添加
             */
            //检测w是否已经在树中,在,则进行下一次循环,寻找与顶点v相连的不在树中的顶点
            if (marked[w]) {
                continue;
            }

            // 不在,则判断此边e的权重是否<此顶点w目前到最小生成树的权重
            if (e.weight() < distTo[w]) {
                // 修改此顶点w到最小生成树的权重为此边的权重
                distTo[w] = e.weight();
                // 修改此顶点w到最小生成树的边为此条边
                edgeTo[w] = e;
                // 判断pq中的有效横切边是否存在顶点w,存在则进行更新,不存在则进行添加
                if (pq.contains(w)) {
                    pq.changeItem(w, e.weight());
                } else {
                    pq.insert(w, e.weight());
                }
            }
        }
    }

    // 获取最小生成树的所有边
    public Queue<Edge> edges() {
        // 创建队列
        Queue<Edge> edges = new Queue<>();
        // 遍历edgeTo数组,找到每一条边,添加到队列中
        for (int i = 0; i < edgeTo.length; i++) {
            if (edgeTo[i] != null) {
                edges.enqueue(edgeTo[i]);
            }
        }
        return edges;
    }
}
  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在Matlab中,可以使用Kruskal算法或Prim算法来求解带权连通最小生成树。Kruskal算法根据边的权值从小到大进行排序,然后按顺序将边加入最小生成树中,但要确保不形成环路,直到最小生成树中包含了中的全部顶点。而Prim算法则是从某个起始顶点开始,每次选择与当前生成树距离最近的顶点,并将其与生成树的边加入,直到最小生成树包含了中的所有顶点。 以下是使用Matlab实现Kruskal算法求解带权连通最小生成树的示例代码: ```matlab function [minimumSpanningTree, totalWeight = kruskal(graph) n = size(graph, 1); edges = []; for i = 1:n-1 for j = i+1:n if graph(i, j) ~= 0 edges = [edges; i, j, graph(i, j)]; end end end edges = sortrows(edges, 3, 'descend'); % 根据边的权值从大到小排序 parent = 1:n; minimumSpanningTree = zeros(n); totalWeight = 0; for k = 1:size(edges, 1) u = edges(k, 1); v = edges(k, 2); w = edges(k, 3); if find(parent, u) ~= find(parent, v) % 判断是否会形成环路 minimumSpanningTree(u, v) = w; % 将边加入最小生成树 minimumSpanningTree(v, u) = w; totalWeight = totalWeight + w; parent(find(parent, u)) = find(parent, v); % 更新parent数组 end end end ``` 使用上述函数,你可以输入一个邻接矩阵表示的带权连通,然后得到最小生成树的邻接矩阵以及最小生成树的权值之和。 请注意,这只是Kruskal算法的一个简单实现,实际应用中可能需要考虑更多的情况,比如输入的不是连通的情况。同时,你也可以使用Matlab中其他的算法库来求解带权连通最小生成树。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [我们可用“破圈法”求解带权连通无向图的一棵最小代价生成树。所](https://blog.csdn.net/weixin_39947522/article/details/112841432)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [无向带权最小生成树算法——Prim及Kruskal算法思路](https://blog.csdn.net/json_it/article/details/77450835)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

农村小白i

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

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

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

打赏作者

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

抵扣说明:

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

余额充值