PHP資料結構(十一) ——圖的連通性問題與最小生成樹演算法(2)
PHP資料結構(十一)——圖的連通性問題與最小生成樹演算法(2)
(原創內容,轉載請註明來源,謝謝)
再次遇到微信公眾號限制字數3000字的問題。因此將Kruskal演算法放於本文中進行描述。本文接上一篇文章。
4、Kruskal演算法
1)該演算法的時間複雜度為O(eloge),e表示邊的數目,即該演算法的時間複雜度和頂點數目無關。該演算法適用於邊數較少的稀疏網。
2)演算法內容
假設N={V, {E}}是連通網,演算法初始狀態為包含圖中的所有的點,沒有邊的T=(V, {})開始,圖中的每一個頂點自成一個連通分量,重複執行以下操作:
在E中選一條代價最小的邊,如果此邊符合該邊依附在兩個不同的連通分量上的要求,則將此邊加入T,否則找代價第二小的邊。以此類推,直至T中所有頂點都落在同一個連通分量上位置。則TE包含n-1條邊,T=(V, {TE})是最小生成樹。
該演算法需要引入一個二維陣列,記錄任意兩個頂點之間的權值,如果兩個頂點沒有連線,則權值為無窮大。
5、總結
Prim演算法和Kruskal演算法,區別在於從頂點切入還是從邊切入。因此,當頂點較多但邊相對較少時,可以使用Kruskal演算法;反之,頂點較少而邊相對較多時,可以使用Prim演算法。兩個演算法都需要引入一個二維陣列,用於儲存任意兩點間的權值,當兩點沒有連線時,權值為無窮大,表示該點無法直接到達另一點。
6、編碼實現
PHP實現Prim演算法和Kruskal演算法的執行結果如下:
//Kruskal演算法:以邊為依據生成最小生成樹 publicfunction getKruskalResult(){ //進行陣列轉換,將siteWeigh轉成一維陣列'ij'=>weigh的形式 $arrKruskal= $this->changeArray($this->siteWeigh); //對轉換後的陣列進行排序,由從小到大的序列進行排序,便於後面取權值小的邊 $arrKruskal= $this->sortArray($arrKruskal); /*Array( [0] => Array ( [line] => 41 [weigh] => 5 ) [1] => Array ( [line]=> 10 [weigh] => 10 ) [2] => Array ( [line] => 31 [weigh] => 10) [3]=> Array ( [line] => 43 [weigh] => 10 ) [4] => Array ( [line] =>20 [weigh] => 15 ) [5]=> Array ( [line] => 40 [weigh] => 20 ) [6] => Array ( [line] =>32 [weigh] => 25 ) )*/ $nodeNum= count($arrKruskal);//獲取節點數目 $resStack= array();//用於存放結果路徑,格式0=>ij,1=>jk $nodeStack= array();//判斷新取的邊是否在同一個連通分量 //遍歷生成的陣列,因為已經排好序,所以從第一個開始遍歷 //如果遍歷到的邊,兩個節點都已經納入結果集,說明該邊是冗餘的邊 //則該邊不是最小生成樹,跳過,遍歷下一條 foreach($arrKruskalas $line){ $node1= $line['line'][0];//獲取兩個階段的編號 $node2= $line['line'][1]; if(in_array($node1,$nodeStack) && in_array($node2, $nodeStack)){ continue;//判斷兩個節點都在結果集就跳過 } array_push($resStack,$line);//邊進結果集 if(!in_array($node1,$nodeStack)){ array_push($nodeStack,$node1);//邊的節點進結果集 } if(!in_array($node2,$nodeStack)){ array_push($nodeStack,$node2); } } //計算權值 $sum= 0; foreach($resStackas $nodeNum => $node){ $sum+= $node['weigh']; } returnarray('resRoad' => $resStack, 'resSum' => $sum); } //提供給kruskal演算法的陣列轉換,將二維陣列轉換成一維陣列 privatefunction changeArray($arrToChange){ $res= array(); $nodeNum= count($arrToChange); for($i=0;$i<$nodeNum;$i++){ //因為陣列是三角對稱的,只需要遍歷半邊即可 //並且i==j即單個頂點的情況不需要遍歷 for($j=0;$j<$i;$j++){ if(999== $arrToChange[$i][$j]){ continue;//不存在的線不需要遍歷 } //$i.$j表示邊ij $res[]= array('line' => $i.$j, 'weigh' => $arrToChange[$i][$j]); } } return$res; } //提供給kruskal演算法的陣列排序,採用快速排序的思想 privatefunction sortArray($arrToSort){ $res= array(); $nodeNum= count($arrToSort);//獲取節點總數 $firstNode= array_shift($arrToSort);//取第一個節點,用於快速排序 $leftSmallArr= array();//存放比第一個節點小的 $rightBigArr= array();//存放比第一個節點大的 foreach($arrToSortas $node){ if($node['weigh']< $firstNode['weigh']){ array_push($leftSmallArr,$node);//小於第一個節點的分到左邊陣列,否則去右邊陣列 }else{ array_push($rightBigArr,$node); } } $leftRes= array(); if(count($leftSmallArr)> 1){ $leftRes= $this->sortArray($leftSmallArr);//當陣列多個元素,呼叫函式再次排序 }elseif(1 == count($leftSmallArr)){ $leftRes= $leftSmallArr;//當陣列只有一個元素,則返回此元素 }else{ } //同leftRes $rightRes= array(); if(count($rightBigArr)>= 1){ $rightRes= $this->sortArray($rightBigArr); }elseif(1 == count($rightBigArr)){ $rightRes= $rightBigArr; }else{ } returnarray_merge($leftRes, array($firstNode), $rightRes); } $minTree = new MinTree(); $kruskalRes = $minTree->getKruskalResult(); echo '採用kruskal演算法,獲取的最小生成樹為:'; print_r($kruskalRes['resRoad']); echo '<br />最終的權值和為:'.$primRes['resSum'].'<br/>';
題外話:兩種最小生成樹演算法,Prim以節點為切入點獲取最小生成樹,Kruskal以邊為切入點獲取最小生成樹。兩者實現方式較為不同,Prim演算法主要以棧的思想進行解決,因此實際編碼過程中進出棧的處理邏輯需要理清楚;Kruskal重在排序,當每條邊的長度排好時,其他問題迎刃而解。
——written by linhxx 2017.07.09