数据结构のJavaScript 实现之最小生成树MST( Kruskal算法和Prim算法)

先感谢查阅博客时给予我启发的一些博主:

  1. https://www.cnblogs.com/skywang12345/p/3711496.html 这篇问题说得比较清楚,kruskal算法的重点在于怎么判断是否形成了回路,而判断是否形成回路要用到并查集的概念。
  2. https://www.cnblogs.com/zhangming-blog/p/5414514.html 这篇例子很好,prim算法讲的很清晰,配图画的很好哈哈。
  3. https://www.cnblogs.com/xzxl/p/7226557.html 这篇关于并查集的讲解十分生动有趣,受到广泛好评,必须一看。
  4. https://github.com/trekhleb/javascript-algorithms/blob/master/README.zh-CN.md 这是github上JavaScript算法实现集合

然后,说下我对两个算法的理解:最大的区别 :Prim算法是“按点生成”,Kruskal算法是“按边生成”.

Prim算法,将所有顶点分成两坨,一坨是用过的点,一坨是还没用的。任意选一个点开始,他就是第一个用过的点,从这个点找权最小的边,通过这个边找到另一个点,把这个点也归入到用过的点集合中。以此类推,每增加一边就增加一个用过的点。

Kruskal算法,将所有边按权值大小排列,从中选最小的边,如果遇到成环的就丢掉,继续下一个最小权重的边。

JavaScript简单实现

定义图形(就是上面链接1里的图)

// 顶点名称
let vertices = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
// 边和权重 [0, 1, 12] 表示A到B的权为12
let edges = [
    [0, 1, 12],
    [0, 5, 16],
    [0, 6, 14],
    [1, 2, 10],
    [1, 5, 7],
    [2, 3, 3],
    [2, 4, 5],
    [2, 5, 6],
    [3, 4, 4],
    [4, 5, 2],
    [4, 6, 8],
    [5, 6, 9]
];

定义两个方法

/**
 * @description 找到最小权重的边
 * @param {Array<edge>} edges
 * @returns {edge} edge
 */
function findMinEdge(edges) {
    let min = null;
    for (const edge of edges) {
        min = min ? edge[2] < min[2] ? edge : min : edge;
    }
    return min;
}
/**
 * @description 找到两组点集合之间的所有边,这个方法只有prim算法用得到
 * @param {Array<vertex>} srcs 源顶点数组
 * @param {Array<vertex>} objs 目标顶点数组
 * @param {Array<edge>} edges 所有边
 * @param {Array<vertex>} vertices 所有顶点
 * @returns {Array} edges
 */
function findEdgesIn(srcs, objs, edges, vertices) {
    let edgesBetweenSrcObj = [];
    for (const edge of edges) {
        for (const src of srcs) {
            srcIndex = vertices.indexOf(src);
            for (const obj of objs) {
                objIndex = vertices.indexOf(obj);
                if (edge[0] === srcIndex && edge[1] === objIndex || edge[0] === objIndex && edge[1] === srcIndex) { // 无向
                    edgesBetweenSrcObj.push(edge);
                }
            }
        }
    }
    return edgesBetweenSrcObj;
}

prim算法实现

function prim(edges, vertices, startVertex) {
    // 命名灵感:僵尸传染人类,找到最小代价,已经传染的人成为僵尸
    let infected = []; // 顶点
    let remained = vertices.slice(0); // 顶点
    let mstree = []; // 边
    let cur = startVertex;
    while (remained.length !== 1) {
        infected.push(cur);
        remained.splice(remained.indexOf(cur), 1);
        let min = findMinEdge(findEdgesIn(infected, remained, edges, vertices));
        mstree.push(min);
        cur = infected.indexOf(vertices[min[0]]) === -1 ? vertices[min[0]] : vertices[min[1]]; // 更新
    }
    return mstree;
}


// 打印树
let mstree = prim(edges, vertices, 'A');
console.log(mstree);

并查集的简单实现(没有实现路径压缩,直接让A扎向B的屁股)
最重要的三个方法:1.makeSet(根据顶点初始化并查集) 2. unionSet(union两个点集) 3. find(找到一个点的掌门人)

    // 非连通集
    function DisjoinSet() {
        this.items = {};
        this.makeSet = function (vertices) {
            for (const vertex of vertices) {
                this.items[vertex] = {
                    parent: null,
                    value: vertex
                };
            }
        }
        this.unionSet = function (vertex_1, vertex_2) {
            const rootA = this.find(vertex_1);
            const rootB = this.find(vertex_2);
            if (rootA === null || rootB === null) {
                throw new Error('no exist vertex');
            }

            if (rootA !== rootB) {
                rootA.parent = rootB
                return true;
            }
            return false;
        }
        this.find = function (vertex) {
            let p = this.items[vertex];
            if (p) {
                return p.parent === null ? p : this.find(p.parent.value);
            }
            throw new Error('not exist vertex');
        }
    }

kruskal算法实现

function kruskal(edges, vertices) {
    let mstree = [];
    let edgesCopy = edges.slice(0);
    let disjoinSet = new DisjoinSet();
    disjoinSet.makeSet(vertices);
    while (mstree.length < vertices.length - 1) {
        let min = findMinEdge(edgesCopy);
        // 不构成环,并查集的理念,为每一个顶点指定一个终点,判断新加两点的终点是否一致
        if (disjoinSet.unionSet(vertices[min[0]], vertices[min[1]])) {
            mstree.push(min);
        }
        edgesCopy.splice(edgesCopy.indexOf(min), 1);
    }
    return mstree;
}


// 打印
let mstree = kruskal(edges, vertices);
console.log(mstree);

两种算法殊途同归,最终得到的结果应该是一致的

[ [ 0, 1, 12 ],
  [ 1, 5, 7 ],
  [ 4, 5, 2 ],
  [ 3, 4, 4 ],
  [ 2, 3, 3 ],
  [ 4, 6, 8 ] ]


 

全部评论

相关推荐

点赞 收藏 评论
分享
牛客网
牛客企业服务