您好,登錄后才能下訂單哦!
之前都是看書(shū),大部分也是c++的實(shí)現(xiàn),但是搞前端不能忘了JS啊,所以JS實(shí)現(xiàn)一遍這兩個(gè)經(jīng)典的最小生成樹(shù)算法。
一、權(quán)重圖和最小生成樹(shù)
權(quán)重圖:圖的邊帶權(quán)重
最小生成樹(shù):在連通圖的所有生成樹(shù)中,所有邊的權(quán)重和最小的生成樹(shù)
本文使用的圖如下:
它的最小生成樹(shù)如下:
二、鄰接矩陣
鄰接矩陣:用來(lái)表示圖的矩陣就是鄰接矩陣,其中下標(biāo)表示頂點(diǎn),矩陣中的值表示邊的權(quán)重(或者有無(wú)邊,方向等)。
本文在構(gòu)建鄰接矩陣時(shí),默認(rèn)Number.MAX_SAFE_INTEGER表示兩個(gè)節(jié)點(diǎn)之間沒(méi)有邊,Number.MIN_SAFE_INTEGER表示當(dāng)前節(jié)點(diǎn)沒(méi)有自環(huán)。
代碼如下:
/** * 鄰接矩陣 * 值為頂點(diǎn)與頂點(diǎn)之間邊的權(quán)值,0表示無(wú)自環(huán),一個(gè)大數(shù)表示無(wú)邊(比如10000) * */ const MAX_INTEGER = Number.MAX_SAFE_INTEGER;//沒(méi)有的邊 const MIN_INTEGER = Number.MIN_SAFE_INTEGER;//沒(méi)有自環(huán) 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] ];
這個(gè)鄰接矩陣表示的圖如下:
三、 邊的表示
一個(gè)邊具有權(quán)重、起點(diǎn)、重點(diǎn)三個(gè)屬性,所以可以創(chuàng)建一個(gè)類(lèi)(對(duì)象),實(shí)現(xiàn)如下:
/** * 邊對(duì)象 * */ 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這門(mén)語(yǔ)言沒(méi)有私有變量的說(shuō)法,這里寫(xiě)get方法純粹是模擬一下私有變量??梢圆挥眠@么寫(xiě),可以直接通過(guò)屬性訪問(wèn)到屬性值。
四、Prim算法
將這個(gè)算法的文章數(shù)不勝數(shù),這里就不細(xì)說(shuō)了。
其大體思路就是:以某頂點(diǎn)為起點(diǎn),逐步找各頂點(diǎn)上最小權(quán)值的相鄰邊構(gòu)建最小生成樹(shù),同時(shí)其鄰接點(diǎn)納入生成樹(shù)的頂點(diǎn)中,只要保證頂點(diǎn)不重復(fù)添加即可。
實(shí)現(xiàn)代碼如下:
/** * Prim算法 * 以某頂點(diǎn)為起點(diǎn),逐步找各頂點(diǎn)上最小權(quán)值的邊構(gòu)建最小生成樹(shù),同時(shí)其鄰接點(diǎn)納入生成樹(shù)的頂點(diǎn)中,只要保證頂點(diǎn)不重復(fù)添加即可 * 使用鄰接矩陣即可 * 優(yōu)點(diǎn):適合點(diǎn)少邊多的情況 * @param matrix 鄰接矩陣 * @return Array 最小生成樹(shù)的邊集數(shù)組 * */ function prim(matrix) { const rows = matrix.length, cols = rows, result = [], savedNode = [0];//已選擇的節(jié)點(diǎn) 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; } } //保證所有已保存節(jié)點(diǎn)的相鄰邊都遍歷到 if (savedNode.indexOf(minVex) === -1 && i === savedNode.length - 1) { savedNode.push(minVex); result.push(new Edge(row, minVex, minWeight)); //重新在已加入的節(jié)點(diǎn)集中找權(quán)值最小的邊的外部邊 i = -1; minWeight = MAX_INTEGER; //已加入的邊,去掉,下次就不會(huì)選這條邊了 matrix[row][minVex] = MAX_INTEGER; matrix[minVex][row] = MAX_INTEGER; } } return result; }
五、Kruskal算法
介紹這個(gè)算法的文章也很多,這里不細(xì)說(shuō)。
其主要的思路就是:遍歷所有的邊,按權(quán)值從小到大排序,每次選取當(dāng)前權(quán)值最小的邊,只要不構(gòu)成回環(huán),則加入生成樹(shù)。
5.1 鄰接矩陣轉(zhuǎn)成邊集數(shù)組
與Prim算法不同,Kruskal算法是從最小權(quán)值的邊開(kāi)始的,所以使用邊集數(shù)組更方便。所以需要將鄰接矩陣轉(zhuǎn)成邊集數(shù)組,并且按照邊的權(quán)重從小到大排序。
/** * 鄰接矩陣轉(zhuǎn)邊集數(shù)組的函數(shù) * @param matrix 鄰接矩陣 * @return Array 邊集數(shù)組 * */ 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算法的具體實(shí)現(xiàn)
Kruskal算法的一個(gè)要點(diǎn)就是避免環(huán)路,這里采用一個(gè)數(shù)組來(lái)保存已納入生成樹(shù)的頂點(diǎn)和邊(連線),其下標(biāo)是邊(連線)的起點(diǎn),下標(biāo)對(duì)應(yīng)的元素值是邊(連線)的終點(diǎn)。下標(biāo)對(duì)應(yīng)的元素值為0,表示還沒(méi)有以它為起點(diǎn)的邊(連線)。
連線:表示一條或多條邊前后連接形成的一條線,這條線沒(méi)有環(huán)路。
/** * kruskal算法 * 遍歷所有的邊,按權(quán)值從小到大排序,每次選取當(dāng)前權(quán)值最小的邊,只要不構(gòu)成回環(huán),則加入生成樹(shù) * 鄰接矩陣轉(zhuǎn)換成邊集數(shù)組 * 優(yōu)點(diǎn):適合點(diǎn)多邊少的情況 * @param matrix 鄰接矩陣 * @return Array 最小生成樹(shù)的邊集數(shù)組 * */ function kruskal(matrix) { const edgeArray = changeMatrixToEdgeArray(matrix), result = [], //使用一個(gè)數(shù)組保存當(dāng)前頂點(diǎn)的邊的終點(diǎn),0表示還沒(méi)有已它為起點(diǎn)的邊加入 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); //不相等表示這條邊沒(méi)有與現(xiàn)有生成樹(shù)形成環(huán)路 if (n !== m) { result.push(edge); //將這條邊的結(jié)尾頂點(diǎn)加入數(shù)組中,表示頂點(diǎn)已在生成樹(shù)中 savedEdge[n] = m; } } return result; } /** * 查找連線頂點(diǎn)的尾部下標(biāo) * @param arr 判斷邊與邊是否形成環(huán)路的數(shù)組 * @param start 連線開(kāi)始的頂點(diǎn) * @return Number 連線頂點(diǎn)的尾部下標(biāo) * */ function findEnd(arr, start) { //就是一直循環(huán),直到找到終點(diǎn),如果沒(méi)有連線,就返回0 while (arr[start] > 0) { start = arr[start]; } return start; }
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。