JavaScript实现Prim与Kruskal最小生成树算法详解
本文使用JavaScript语言实现Prim和Kruskal两种经典的最小生成树算法。文章首先介绍权重图与邻接矩阵的构建,随后详细讲解Prim算法从顶点出发逐步扩展的逻辑及Kruskal算法基于边集排序并避免回环的实现方式,包含完整代码示例与图解。
JavaScript实现Prim与Kruskal最小生成树算法详解
以往阅读书籍时,多数示例采用C++语言编写,但作为前端开发者,使用JavaScript重新实现这两个经典的最小生成树算法显得尤为必要。
权重图与最小生成树概念
一、权重图和最小生成树
权重图是指图中每条边都带有特定的权重值。
最小生成树是指在连通图的所有可能生成树中,所有边的权重之和最小的那一棵生成树。
本文所采用的示例图如下所示:

该图对应的最小生成树结构如下:

图的邻接矩阵表示法
二、邻接矩阵
邻接矩阵是一种用于表示图的矩阵数据结构,其中矩阵的下标代表顶点,矩阵中的数值代表边之间的权重(或表示是否存在边、边的方向等信息)。
在构建本文的邻接矩阵时,约定使用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;
}
}*/
注:JavaScript语言本身没有严格的私有变量概念,此处编写get方法仅为模拟私有变量的访问方式。实际上可以直接通过属性访问获取值,无需如此封装。
Prim算法实现逻辑
四、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算法实现逻辑
五、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;
}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。
JS处理含Emoji或中文的字符串转数组乱码问题
2026-11-05 16:12:28
JS怎么使用展开运算符合并对象
2026-11-05 14:42:18
ES6+环境下判断JS对象为空的简洁写法
2026-11-05 12:12:23
怎么通过replacer函数自定义JS中JSON的格式化输出
2026-11-05 11:19:44
JS正则表达式基础:字面量与RegExp构造函数的区别及常用元字符
2026-11-05 11:16:58
