普里姆算法最小生成树_javascript使用普里姆算法解决修路问题

普里姆算法介绍
1.普利姆(Prim)算法求最小生成树,也就是在包含n个顶点的连通图中,找出只有(n-1)条边包含所有n个顶点的连通子图,也就是所谓的极小连通子图
2.普利姆的算法如下:
>1.设G=(V,E)是连通网,T=(U,D)是最小生成树,V,U是顶点集合,E,D是边的集合
>2.若从顶点u开始构造最小生成树,则从集合V中取出顶点u放入集合U中,标记顶点v的visited[u]=1
>3.若集合U中顶点ui与集合V-U中的顶点vj之间存在边,则寻找这些边中权值最小的边,但不能构成回路,将顶点vj加入集合U中,将边(ui,vj)加入集合D中,标记visited[vj]=1
>4.重复步骤②,直到U与V相等,即所有顶点都被标记为访问过,此时D中有n-1条边
看一个应用场景和问题:

010447a2edc6e1a5f5c7c776de0ab484.png

有胜利乡有7个村庄(A, B, C, D, E, F, G) ,现在需要修路把7个村庄连通

各个村庄的距离用边线表示(权) ,比如 A – B 距离 5公里

问:如何修路保证各个村庄都能连通,并且总的修建公路总里程最短?

思路: 将10条边,连接即可,但是总的里程数不是最小.

正确的思路,就是尽可能的选择少的路线,并且每条路线最小,保证总里程数最少.

修路问题本质就是就是最小生成树问题, 先介绍一下最小生成树(Minimum Cost Spanning Tree),简称MST。

给定一个带权的无向连通图,如何选取一棵生成树,使树上所有边上权的总和为最小,这叫最小生成树

1.N个顶点,一定有N-1条边

2.包含全部顶点

3.N-1条边都在图中

举例说明(如图:)

4f2eb0204c7840c4f455742897b8c251.png

求最小生成树的算法主要是普里姆 算法和克鲁斯卡尔算法

/**
 *
 *
 普利姆算法

普里姆算法介绍

1.普利姆(Prim)算法求最小生成树,也就是在包含n个顶点的连通图中,找出只有(n-1)条边包含所有n个顶点的连通子图,也就是所谓的极小连通子图
2.普利姆的算法如下:

>1.设G=(V,E)是连通网,T=(U,D)是最小生成树,V,U是顶点集合,E,D是边的集合 
>2.若从顶点u开始构造最小生成树,则从集合V中取出顶点u放入集合U中,标记顶点v的visited[u]=1
>3.若集合U中顶点ui与集合V-U中的顶点vj之间存在边,则寻找这些边中权值最小的边,但不能构成回路,将顶点vj加入集合U中,将边(ui,vj)加入集合D中,标记visited[vj]=1
>4.重复步骤②,直到U与V相等,即所有顶点都被标记为访问过,此时D中有n-1条边

分析:
    我们知道最小生成树有n个顶点, 那么肯定只有n-1条边,命名线路为L,线路L是一课树
    1.假设A点为叶子节点,也就是A点和树L中只有有一条边a连接,而这条边a不是连接A周围边中最小的一条min,
此时,我们可以用min代替a,依然能保持L是一颗树,所以此假设不成立,
     假设A不为叶子节点,而A周围边中最小的那条边min没有被使用,那么我们可以将min这条边连接上,
此时,我们可以发现min和A周围另外一条边a连接着树L的一颗子树,
此时,我们可以用min代替a,依然能保持L是一颗树,所以此假设也不成立
因此我们可以确定A点周围边中最小的那条边肯定会被使用,
此时可以确定最短线路L的一条边,那么还剩下n-2条路径需要寻找
    假设此个边连接A,B,将A,B标记为已访问
    2.此时,我们可以将这条边和边相连的两个点AB看做是一个点Q,这个顶点Q满足所有顶点的要求,
AB顶点相邻的边,可以看做是Q点相连的边(注意剔除形成回路的边,因为回路的话等于是Q连接Q,没有意义),
重复1,我们就能确定第二条边是相邻边中最小的那条,
,才能保证L线路最短,那么此时线路L确定了两条边,还剩n-3条边需要寻找,
     也就是从A,B两个点能连通的未访问点中,找到边路径最小的那个点C,标记C为已访问
    3.再次从重复2,1,2,1
    也就是从A,B,C点能连通的未访问点中,找到边路径最小的那个点D,标记D为已访问
    4,重复上述步骤,知道确定n个顶点,n-1条边
    

 *
 */

//创建最小生成树
class MinTree {
    //创建图的邻接矩阵
    /**
     * 
     * @param {图对象} graph 
     * @param {图对应的顶点个数} verxs 
     * @param {图的各个顶点的值} data 
     * @param {图的邻接矩阵} weight 
     */
    createGraph(graph, verxs, data, weight) {
        let i, j;
        for (let i = 0; i < verxs; i++) {//顶点
            graph.data[i] = data[i];
            for (j = 0; j < verxs; j++) {
                graph.weight[i][j] = weight[i][j]
            }
        }
    }

    //编写prim算法,得到最小生成树
    /**
     * 
     * @param {图} graph 
     * @param {表示从图的第几个顶点开始生成} v 
     */
    prim(graph, v) {
        //标记节点是否被访问过,默认都没有访问过
        let visited = new Array(graph.verxs).fill(false);
        //把当前这个节点标记为已访问
        visited[v] = true;
        let h1 = -1, h2 = -2;
        //初始化成一个大树,后面在遍历过程中会被替换
        let minWeight = 1000;
        //记录边
        let line = [];
        //因为有graph.verxs个顶点,那么就会有graph.verxs-1条边
        for (let k = 1; k < graph.verxs; k++) {

            //遍历图graph.weight,找到与已访问的节点visited[i]连通的
            //未访问的节点visited[j],并且找到i与j相连的边中权值(距离)最小的那条边
            for (let i = 0; i < graph.verxs; i++) {//已访问的节点visited[i]
                for (let j = 0; j < graph.verxs; j++) {//未访问的节点visited[j]
                    if (visited[i] && !visited[j] && graph.weight[i][j] < minWeight) {
                        //替换minWeight
                        minWeight = graph.weight[i][j];
                        //记录i与j相连的边中权值(距离)最小的那条边
                        h1 = i;
                        h2 = j;
                    }
                }
            }
            line.push(`<${mGraph.data[h1]}-${mGraph.data[h2]}>相连,长度为:${minWeight}`);
            //将访问到的j点标记为已访问
            visited[h2] = true;
            //minWeigth 重新设置为10000
            minWeight = 10000;
        }
        return line;
    }
}

class MGraph {
    verxs;//表示图的节点个数
    data;//保存节点的数据
    weight;//存放边,就是我们的邻接矩阵
    constructor(verxs) {
        this.verxs = verxs;
        this.data = [];
        this.weight = new Array(verxs);
        for (let i = 0; i < verxs; i++) {
            this.weight[i] = new Array(verxs);
        }
    }
}

//测试图是否创建ok
let data = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];

let verxs = data.length;
//邻接矩阵的关系使用二维数组表示
let weight = new Array(verxs);
for (let i = 0; i < verxs; i++) {
    weight[i] = new Array(verxs).fill(10000);
}
//创建路径
let A = 0, B = 1, C = 2, D = 3, E = 4, F = 5, G = 6;
weight[A][B] = weight[B][A] = 5;//A-B = 5
weight[A][C] = weight[C][A] = 7;//A-C = 7
weight[A][G] = weight[G][A] = 2;//A-G = 2
weight[B][D] = weight[D][B] = 9;//B-D = 9
weight[B][G] = weight[G][B] = 3;//B-G = 3
weight[C][E] = weight[E][C] = 8;//C-E = 8
weight[D][F] = weight[F][D] = 4;//D-F = 4
weight[E][F] = weight[F][E] = 5;//E-F = 5
weight[F][G] = weight[G][F] = 6;//F-G = 6
weight[G][E] = weight[E][G] = 4;//G-E = 4

//创建MGraph对象
let mGraph = new MGraph(verxs);
//创建一个MinTree对象
let minTree = new MinTree();
minTree.createGraph(mGraph, verxs, data, weight);
//console.log(mGraph);
console.log(minTree.prim(mGraph, 0))

测试:

0b63710f999b772555a9dc8f43b736c3.png

r

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值