最小生成树问题
普利姆算法(加点法)
- 任选一个点为起点
- 找到以当前点为起点的路径最短的边
- 如果这个边的另一端没有被连接起来,则连接
- 如果这个边的另一端被连接起来了,则看倒数第二短的边
- 重复2-4
克鲁斯卡尔算法(加边法)
- 先找到最短的边,连接两端的点
- 找到第二短的边连接两端的点,要求这条边的两端至少有一个未被连接的点
- 或者这个边是将两个部落进行连接
- 重复1-3
-
先来一张图
-
将这张图翻译成代码
| A | B | C | D | E | |
|---|---|---|---|---|---|
| A | 0 | 4 | 7 | MAX | MAX |
| B | 4 | 0 | 8 | 6 | MAX |
| C | 7 | 8 | 0 | 5 | MAX |
| D | MAX | 6 | 5 | 0 | 7 |
| E | MAX | MAX | MAX | 7 | 0 |
var max = Number.MAX_SAFE_INTEGER;
var distance = [
[0, 4, 7, max, max],
[4, 0, 8, 6, max],
[7, 8, 0, 5, max],
[max, 6, 5, 0, 7],
[max, max, max, 7, 0]
];
// 这是class, ts编译之后生成的样子,不用在意
var DisNode = /** @class */ (function () {
function DisNode(val) {
this.value = null;
this.neighbor = [];
this.value = val;
}
return DisNode;
}());
var A = new DisNode('A');
var B = new DisNode('B');
var C = new DisNode('C');
var D = new DisNode('D');
var E = new DisNode('E');
var pointSet = [];
pointSet.push(A, B, C, D, E);
普利姆算法
function getIndex(str) {
for (var i = 0; i < pointSet.length; i++) {
if (str == pointSet[i].value)
return i;
}
return -1;
}
/**
* 得到一个当前最小距离的点
* @param {*} pointSet 点的集合
* @param {*} distance 边的集合
* @param {*} nowPointSet 当前已经连接的点的集合
*/
function getMinDisNode(pointSet, distance, nowPointSet) {
var formNode = null; //线段的起点
var minDisNode = null; // 线段的终点
var minDis = max;
// 根据当前已有的这些点为起点,依次判断连接其他的点的距离是多少
for (var i = 0; i < nowPointSet.length; i++) {
// 获取当前节点的序号
var nowPointIdx = getIndex(nowPointSet[i].value);
for (var j = 0; j < distance[nowPointIdx].length; j++) {
var thisNode = pointSet[j];
if (nowPointSet.indexOf(thisNode) < 0 && distance[nowPointIdx][j] < minDis) {
formNode = nowPointSet[i];
minDisNode = thisNode;
minDis = distance[nowPointIdx][j];
}
}
}
formNode.neighbor.push(minDisNode);
minDisNode.neighbor.push(formNode);
return minDisNode;
}
/**
* 普利姆算法
* @param {*} pointSet 点集合
* @param {*} distance 点之间的距离集合
* @param {*} start 开始点
*/
function prim(pointSet, distance, start) {
var nowPointSet = []; // 现在已经连接的点
nowPointSet.push(start);
while (true) {
var minDisNode = getMinDisNode(pointSet, distance, nowPointSet); // 获取最短距离的点
nowPointSet.push(minDisNode);
if (nowPointSet.length == pointSet.length)
break;
}
}
prim(pointSet, distance, pointSet[2]);
克鲁斯卡尔算法
function canLink(resultList, startPoint, endPoint) {
var beginIn = null,
endIn = null;
for (var i = 0; i < resultList.length; i++) {
if (resultList[i].indexOf(startPoint) != -1) {
beginIn = resultList[i]
}
if (resultList[i].indexOf(endPoint) != -1) {
endIn = resultList[i]
}
}
// 如果开始点和结束点在同一个部落数组中,则不能连接,因为已经连接了
if (beginIn != null && endIn != null && beginIn == endIn) {
return false
}
return true
}
function link(resultList, startPoint, endPoint) {
var beginIn = null,
endIn = null;
for (var i = 0; i < resultList.length; i++) {
if (resultList[i].indexOf(startPoint) != -1) {
beginIn = resultList[i]
}
if (resultList[i].indexOf(endPoint) != -1) {
endIn = resultList[i]
}
}
if (beginIn == null && endIn != null) {
// 将开始点 接入 结束点的部落数组中
endIn.push(startPoint)
} else if (beginIn != null && endIn == null) {
// 将结束点 接入 开始的部落数组中
beginIn.push(endPoint)
} else if (beginIn == null && endIn == null) {
// 将两个点相连
resultList.push([startPoint, endPoint])
} else if (beginIn != null && endIn != null && beginIn != endIn) {
// 两个部落连接
var allIn = beginIn.concat(endIn)
resultList.splice(resultList.indexOf(beginIn), 1)
resultList.splice(resultList.indexOf(endIn), 1)
resultList.push(allIn)
}
startPoint.neighbor.push(endPoint)
endPoint.neighbor.push(startPoint)
}
/**
* 克鲁斯科尔算法
* @param {*} pointSet 点集合
* @param {*} distance 点距离集合
*/
function kruskal(pointSet, distance) {
var resultList = [];
while (true) {
var minDis = max,
startPoint = null,
endPoint = null;
for (var i = 0; i < pointSet.length; i++) {
for (var j = 0; j < distance[i].length; j++) {
var tempStart = pointSet[i],
tempEnd = pointSet[j];
if (
i != j &&
distance[i][j] < minDis &&
canLink(resultList, tempStart, tempEnd)
) {
startPoint = tempStart
endPoint = tempEnd
minDis = distance[i][j]
}
}
}
link(resultList, startPoint, endPoint)
if (resultList.length == 1 && resultList[0].length == pointSet.length) {
break
}
}
}
kruskal(pointSet, distance)
写在结尾
克鲁斯卡尔算法和普利姆算法都还是比较复杂的算法,我这里实现的方法,循环次数有些多,希望看到的大佬能给予指正。
其实在前端面试中,这两种算法一般不会考代码实现,一般来说会是一道填空题,写出连接顺序。