JS使用Prim算法和Kruskal算法实现最小生成树


Posted in Javascript onJanuary 17, 2019

之前都是看书,大部分也是c++的实现,但是搞前端不能忘了JS啊,所以JS实现一遍这两个经典的最小生成树算法。

一、权重图和最小生成树

权重图:图的边带权重

最小生成树:在连通图的所有生成树中,所有边的权重和最小的生成树

本文使用的图如下:

JS使用Prim算法和Kruskal算法实现最小生成树

它的最小生成树如下:

JS使用Prim算法和Kruskal算法实现最小生成树

二、邻接矩阵

邻接矩阵:用来表示图的矩阵就是邻接矩阵,其中下标表示顶点,矩阵中的值表示边的权重(或者有无边,方向等)。

本文在构建邻接矩阵时,默认Number.MAX_SAFE_INTEGER表示两个节点之间没有边,Number.MIN_SAFE_INTEGER表示当前节点没有自环。

代码如下:

/**
 * 邻接矩阵
 * 值为顶点与顶点之间边的权值,0表示无自环,一个大数表示无边(比如10000)
 * */
const MAX_INTEGER = Number.MAX_SAFE_INTEGER;//没有的边
const MIN_INTEGER = Number.MIN_SAFE_INTEGER;//没有自环
 
const matrix= [
  [MIN_INTEGER, 9, 2, MAX_INTEGER, 6],
  [9, MIN_INTEGER, 3, MAX_INTEGER, MAX_INTEGER],
  [2, 3, MIN_INTEGER, 5, MAX_INTEGER],
  [MAX_INTEGER, MAX_INTEGER, 5, MIN_INTEGER, 1],
  [6, MAX_INTEGER, MAX_INTEGER, 1, MIN_INTEGER]
];

这个邻接矩阵表示的图如下:

三、 边的表示

一个边具有权重、起点、重点三个属性,所以可以创建一个类(对象),实现如下:

/**
 * 边对象
 * */
function Edge(begin, end, weight) {
  this.begin = begin;
  this.end = end;
  this.weight = weight;
}
 
Edge.prototype.getBegin = function () {
  return this.begin;
};
Edge.prototype.getEnd = function () {
  return this.end;
};
Edge.prototype.getWeight = function () {
  return this.weight;
};
 
/*class Edge {
  constructor(begin, end, weight) {
    this.begin = begin;
    this.end = end;
    this.weight = weight;
  }
  getBegin() {
    return this.begin;
  }
  getEnd() {
    return this.end;
  }
  getWeight() {
    return this.weight;
  }
}*/

 PS:JS这门语言没有私有变量的说法,这里写get方法纯粹是模拟一下私有变量。可以不用这么写,可以直接通过属性访问到属性值。

四、Prim算法

将这个算法的文章数不胜数,这里就不细说了。

其大体思路就是:以某顶点为起点,逐步找各顶点上最小权值的相邻边构建最小生成树,同时其邻接点纳入生成树的顶点中,只要保证顶点不重复添加即可。

实现代码如下:

/**
 * Prim算法
 * 以某顶点为起点,逐步找各顶点上最小权值的边构建最小生成树,同时其邻接点纳入生成树的顶点中,只要保证顶点不重复添加即可
 * 使用邻接矩阵即可
 * 优点:适合点少边多的情况
 * @param matrix 邻接矩阵
 * @return Array 最小生成树的边集数组
 * */
function prim(matrix) {
  const rows = matrix.length,
    cols = rows,
    result = [],
    savedNode = [0];//已选择的节点
  let minVex = -1,
    minWeight = MAX_INTEGER;
  for (let i = 0; i < rows; i++) {
    let row = savedNode[i],
      edgeArr = matrix[row];
    for (let j = 0; j < cols; j++) {
      if (edgeArr[j] < minWeight && edgeArr[j] !== MIN_INTEGER) {
        minWeight = edgeArr[j];
        minVex = j;
      }
    }
 
    //保证所有已保存节点的相邻边都遍历到
    if (savedNode.indexOf(minVex) === -1 && i === savedNode.length - 1) {
      savedNode.push(minVex);
      result.push(new Edge(row, minVex, minWeight));
 
      //重新在已加入的节点集中找权值最小的边的外部边
      i = -1;
      minWeight = MAX_INTEGER;
 
      //已加入的边,去掉,下次就不会选这条边了
      matrix[row][minVex] = MAX_INTEGER;
      matrix[minVex][row] = MAX_INTEGER;
    }
  }
  return result;
}

五、Kruskal算法

介绍这个算法的文章也很多,这里不细说。

其主要的思路就是:遍历所有的边,按权值从小到大排序,每次选取当前权值最小的边,只要不构成回环,则加入生成树。

5.1 邻接矩阵转成边集数组

与Prim算法不同,Kruskal算法是从最小权值的边开始的,所以使用边集数组更方便。所以需要将邻接矩阵转成边集数组,并且按照边的权重从小到大排序。

/**
 * 邻接矩阵转边集数组的函数
 * @param matrix 邻接矩阵
 * @return Array 边集数组
 * */
function changeMatrixToEdgeArray(matrix) {
  const rows = matrix.length,
    cols = rows,
    result = [];
  for (let i = 0; i < rows; i++) {
    const row = matrix[i];
    for(let j = 0 ; j < cols; j++) {
      if(row[j] !== MIN_INTEGER && row[j] !== MAX_INTEGER) {
        result.push(new Edge(i, j, row[j]));
        matrix[i][j] = MAX_INTEGER;
        matrix[j][i] = MAX_INTEGER;
      }
    }
  }
  result.sort((a, b) => a.getWeight() - b.getWeight());
  return result;
}

5.2 Kruskal算法的具体实现

Kruskal算法的一个要点就是避免环路,这里采用一个数组来保存已纳入生成树的顶点和边(连线),其下标是边(连线)的起点,下标对应的元素值是边(连线)的终点。下标对应的元素值为0,表示还没有以它为起点的边(连线)。

连线:表示一条或多条边前后连接形成的一条线,这条线没有环路。

/**
 * kruskal算法
 * 遍历所有的边,按权值从小到大排序,每次选取当前权值最小的边,只要不构成回环,则加入生成树
 * 邻接矩阵转换成边集数组
 * 优点:适合点多边少的情况
 * @param matrix 邻接矩阵
 * @return Array 最小生成树的边集数组
 * */
function kruskal(matrix) {
  const edgeArray = changeMatrixToEdgeArray(matrix),
    result = [],
    //使用一个数组保存当前顶点的边的终点,0表示还没有已它为起点的边加入
    savedEdge = new Array(matrix.length).fill(0);
 
  for (let i = 0, len = edgeArray.length; i < len; i++) {
    const edge = edgeArray[i];
    const n = findEnd(savedEdge, edge.getBegin());
    const m = findEnd(savedEdge, edge.getEnd());
    console.log(savedEdge, n, m);
    //不相等表示这条边没有与现有生成树形成环路
    if (n !== m) {
      result.push(edge);
      //将这条边的结尾顶点加入数组中,表示顶点已在生成树中
      savedEdge[n] = m;
    }
  }
  return result;
}
 
/**
 * 查找连线顶点的尾部下标
 * @param arr 判断边与边是否形成环路的数组
 * @param start 连线开始的顶点
 * @return Number 连线顶点的尾部下标
 * */
function findEnd(arr, start) {
  //就是一直循环,直到找到终点,如果没有连线,就返回0
  while (arr[start] > 0) {
    start = arr[start];
  }
  return start;
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
IE event.srcElement和FF event.target 功能比较
Mar 01 Javascript
通过复制Table生成word和excel的javascript代码
Jan 20 Javascript
javascript中clipboardData对象用法详解
May 13 Javascript
Javascript 是你的高阶函数(高级应用)
Jun 15 Javascript
Javascript中 带名 匿名 箭头函数的重要区别(推荐)
Jan 29 Javascript
JavaScript html5利用FileReader实现上传功能
Mar 27 Javascript
浅谈关于.vue文件中style的scoped属性
Aug 19 Javascript
在vue里面设置全局变量或数据的方法
Mar 09 Javascript
vue自定义全局共用函数详解
Sep 18 Javascript
详解vue2.6插槽更新v-slot用法总结
Mar 09 Javascript
vue+webpack dev本地调试全局样式引用失效的解决方案
Nov 12 Javascript
vue3弹出层V3Popup实例详解
Jan 04 Vue.js
微信小程序使用wxParse解析html的方法示例
Jan 17 #Javascript
nvm、nrm、npm 安装和使用详解(小结)
Jan 17 #Javascript
JavaScript之实现一个简单的Vue示例
Jan 17 #Javascript
如何能分清npm cnpm npx nvm
Jan 17 #Javascript
JavaScript设计模式之装饰者模式实例详解
Jan 17 #Javascript
npm 常用命令详解(小结)
Jan 17 #Javascript
JavaScript设计模式之享元模式实例详解
Jan 17 #Javascript
You might like
php提示Failed to write session data错误的解决方法
2014/12/17 PHP
php脚本守护进程原理与实现方法详解
2017/07/20 PHP
使用Git实现Laravel项目的自动化部署
2019/11/24 PHP
Laravel框架处理用户的请求操作详解
2019/12/20 PHP
javascript add event remove event
2008/04/07 Javascript
基于JQuery的密码强度验证代码
2010/03/01 Javascript
jQuery html() in Firefox (uses .innerHTML) ignores DOM changes
2010/03/05 Javascript
事件模型在各浏览器中存在差异
2010/10/20 Javascript
jQuery实现可编辑的表格实例讲解(2)
2015/09/17 Javascript
JS实现的仿淘宝交易倒计时效果
2015/11/27 Javascript
jquery利用json实现页面之间传值的实例解析
2016/12/12 Javascript
react-router中的属性详解
2017/06/01 Javascript
JS库之wow.js使用方法
2017/09/14 Javascript
vue实现固定位置显示功能
2019/05/30 Javascript
解决layui数据表格排序图标被超出的表头挤出去的问题
2019/09/19 Javascript
如何修改Vue打包后文件的接口地址配置的方法
2020/04/22 Javascript
封装 axios+promise通用请求函数操作
2020/08/11 Javascript
Python datetime时间格式化去掉前导0
2014/07/31 Python
python写的一个squid访问日志分析的小程序
2014/09/17 Python
python实现八大排序算法(2)
2017/09/14 Python
对Python的多进程锁的使用方法详解
2019/02/18 Python
接口自动化多层嵌套json数据处理代码实例
2020/11/20 Python
收藏!10个免费高清视频素材网站!【设计、视频剪辑必备】
2021/03/18 杂记
Nebula美国官网:便携式投影仪
2019/03/15 全球购物
什么是虚拟内存?虚拟内存有什么优势?
2012/02/19 面试题
三年级音乐教学反思
2014/01/28 职场文书
幼儿园中秋节活动方案2013
2014/01/29 职场文书
大家访活动实施方案
2014/03/10 职场文书
水毁工程实施方案
2014/04/01 职场文书
师德师风建设方案
2014/05/08 职场文书
小学三好学生事迹材料
2014/08/15 职场文书
党员查摆问题及整改措施
2014/10/10 职场文书
村干部群众路线整改措施思想汇报
2014/10/12 职场文书
党员个人自我评价
2015/03/03 职场文书
幼儿园园长安全责任书
2015/05/08 职场文书
煤矿安全学习心得体会
2016/01/18 职场文书