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 相关文章推荐
JS创建优美的页面滑动块效果 - Glider.js
Sep 27 Javascript
实现超用户体验 table排序javascript实现代码
Jun 22 Javascript
动态调用CSS文件的JS代码
Jul 29 Javascript
提高jQuery性能的十个诀窍
Nov 14 Javascript
jquery 无限级下拉菜单的简单实现代码
Feb 21 Javascript
用Jquery选择器计算table中的某一列某一行的合计
Aug 13 Javascript
javascript常用函数(1)
Nov 04 Javascript
详解JavaScript中js对象与JSON格式字符串的相互转换
Feb 14 Javascript
深入理解vue $refs的基本用法
Jul 13 Javascript
Easy UI动态树点击文字实现展开关闭功能
Sep 30 Javascript
前端Electron新手入门教程详解
Jun 21 Javascript
微信小程序如何自定义table组件
Jun 29 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
用PHP控制用户的浏览器--ob*函数的使用说明
2007/03/16 PHP
网页游戏开发入门教程二(游戏模式+系统)
2009/11/02 PHP
基于PHP字符串的比较函数strcmp()与strcasecmp()的使用详解
2013/05/15 PHP
ThinkPHP文件上传实例教程
2014/08/22 PHP
thinkPHP5框架闭包函数与子查询传参用法示例
2018/08/02 PHP
在服务端(Page.Write)调用自定义的JS方法详解
2013/08/09 Javascript
jQuery表单域选择器用法分析
2015/02/10 Javascript
JS实现的自定义水平滚动字体插件完整实例
2016/06/17 Javascript
第一次接触神奇的Bootstrap导航条
2016/08/09 Javascript
JavaScript实现开关等效果
2017/09/08 Javascript
vue-router实现嵌套路由的讲解
2019/01/19 Javascript
Js代码中的span拼接问题解决
2019/11/22 Javascript
JavaScript中跨域问题的深入理解
2021/03/04 Javascript
多线程爬虫批量下载pcgame图片url 保存为xml的实现代码
2013/01/17 Python
python numpy函数中的linspace创建等差数列详解
2017/10/13 Python
浅谈python中字典append 到list 后值的改变问题
2018/05/04 Python
python实现自动发送报警监控邮件
2018/06/21 Python
Python3中在Anaconda环境下安装basemap包
2018/10/21 Python
python之PyQt按钮右键菜单功能的实现代码
2019/08/17 Python
浅析Python数字类型和字符串类型的内置方法
2019/12/22 Python
使用python-pptx包批量修改ppt格式的实现
2020/02/14 Python
Python利用FFT进行简单滤波的实现
2020/02/26 Python
使用python自动追踪你的快递(物流推送邮箱)
2020/03/17 Python
python实现四人制扑克牌游戏
2020/04/22 Python
Pytorch数据拼接与拆分操作实现图解
2020/04/30 Python
Python离线安装各种库及pip的方法
2020/11/28 Python
在HTML5中使用MathML数学公式的简单讲解
2016/02/19 HTML / CSS
BNKR中国官网:带你感受澳洲领先潮流时尚
2018/08/21 全球购物
波兰家居饰品和厨房配件网上商店:Maleomi
2020/12/15 全球购物
小加工厂管理制度
2014/01/21 职场文书
建筑个人求职信范文
2014/01/25 职场文书
同学会主持词
2014/03/18 职场文书
个人自我鉴定总结
2014/03/25 职场文书
优秀乡村医生事迹材料
2014/05/28 职场文书
四风问题自查自纠工作情况报告
2014/10/28 职场文书
mysql通过group by分组取最大时间对应数据的两种有效方法
2022/09/23 MySQL