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 相关文章推荐
javascript 面向对象全新理练之原型继承
Dec 03 Javascript
JavaScript 异步方法队列链实现代码分析
Jun 05 Javascript
Google Dart编程语法和基本类型学习教程
Nov 27 Javascript
关闭浏览器输入框自动补齐 兼容IE,FF,Chrome等主流浏览器
Feb 11 Javascript
js实现从中间开始往上下展开网页窗口的方法
Mar 02 Javascript
jQuery实现给页面换肤的方法
May 30 Javascript
原生js实现autocomplete插件
Apr 14 Javascript
使用JavaScript获取Request中参数的值方法
Sep 27 Javascript
js canvas仿支付宝芝麻信用分仪表盘
Nov 16 Javascript
JavaScript实现京东购物放大镜和选项卡效果的方法分析
Jul 05 Javascript
Vue事件修饰符native、self示例详解
Jul 09 Javascript
echarts浮动显示单位的实现方法示例
Dec 04 Javascript
微信小程序使用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
《逃离塔科夫》——“萌新劝退,老手自嗨”的硬核FPS游戏
2020/04/03 其他游戏
Memcached常用命令以及使用说明详解
2013/06/27 PHP
利用谷歌 Translate API制作自己的翻译脚本
2014/06/04 PHP
Thinkphp框架 表单自动验证登录注册 ajax自动验证登录注册
2016/12/27 PHP
PHP实现登录注册之BootStrap表单功能
2017/09/03 PHP
关于 byval 与 byref 的区别分析总结
2007/10/08 Javascript
jQuery使用动态渲染表单功能完成ajax文件下载
2013/01/15 Javascript
Array 重排序方法和操作方法的简单实例
2014/01/24 Javascript
jQuery中:has选择器用法实例
2014/12/30 Javascript
浅谈javascript 函数属性和方法
2015/01/21 Javascript
javascript实现的登陆遮罩效果汇总
2015/11/09 Javascript
javascript稀疏数组(sparse array)和密集数组用法分析
2016/12/28 Javascript
vue webuploader 文件上传组件开发
2017/09/23 Javascript
NodeJS 中Stream 的基本使用
2018/07/30 NodeJs
解决Vue2.0中使用less给元素添加背景图片出现的问题
2018/09/03 Javascript
JS跨域请求的问题解析
2018/12/03 Javascript
Python实现改变与矩形橡胶的线条的颜色代码示例
2018/01/05 Python
Python中getpass模块无回显输入源码解析
2018/01/11 Python
Android基于TCP和URL协议的网络编程示例【附demo源码下载】
2018/01/23 Python
TensorFlow中权重的随机初始化的方法
2018/02/11 Python
Python中str.join()简单用法示例
2018/03/20 Python
python实现读取大文件并逐行写入另外一个文件
2018/04/19 Python
flask框架视图函数用法示例
2018/07/19 Python
PyTorch中常用的激活函数的方法示例
2019/08/20 Python
美国女性服饰销售网站:Nasty Gal(坏女孩)
2016/07/26 全球购物
Net-A-Porter美国官网:全球时尚奢侈品名站
2017/02/11 全球购物
Theory美国官网:后现代都市风时装品牌
2018/05/09 全球购物
心理健康日活动总结
2014/05/08 职场文书
员工安全承诺书
2014/05/22 职场文书
简单租房协议书范本
2014/08/20 职场文书
酒店七夕情人节活动策划方案
2014/08/24 职场文书
php随机生成验证码,php随机生成数字,php随机生成数字加字母!
2021/04/01 PHP
微信小程序基础教程之echart的使用
2021/06/01 Javascript
golang实现浏览器导出excel文件功能
2022/03/25 Golang
一次Mysql update sql不当引起的生产故障记录
2022/04/01 MySQL
Go gorilla/sessions库安装使用
2022/08/14 Golang