最小生成樹問題
普利姆演算法(加點法)
- 任選一個點為起點
- 找到以當前點為起點的路徑最短的邊
- 如果這個邊的另一端沒有被連線起來,則連線
- 如果這個邊的另一端被連線起來了,則看倒數第二短的邊
- 重複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)
複製程式碼
寫在結尾
克魯斯卡爾演算法和普利姆演算法都還是比較複雜的演算法,我這裡實現的方法,迴圈次數有些多,希望看到的大佬能給予指正。
其實在前端面試中,這兩種演算法一般不會考程式碼實現,一般來說會是一道填空題,寫出連線順序。