1. 程式人生 > 其它 >PHP資料結構(十一) ——圖的連通性問題與最小生成樹演算法(1)

PHP資料結構(十一) ——圖的連通性問題與最小生成樹演算法(1)

PHP資料結構(十一)——圖的連通性問題與最小生成樹演算法(1)

(原創內容,轉載請註明來源,謝謝)

一、連通分量和生成樹

1、無向圖

設E(G)為連通圖G的所有邊的集合,從圖的任意一點出發遍歷圖,可以將E(G)分為T(G)和B(G),T表示已經遍歷過的邊的集合,B表示剩餘邊的集合。因此,T與圖G的所有頂點構成的極小連通子圖,就是G的一棵生成樹。由深度優先搜尋的稱為深度優先生成樹;由廣度優先搜尋的稱為廣度優先生成樹。

2、有向圖

有向圖和無向圖類似。有向圖的強連通分量,是對圖進行深度優先遍歷,遍歷完成後,從被遍歷的最後一個節點開始,做逆向的深度優先遍歷。

二、關節點和重連通分量

1、定義

1)當刪去圖的節點V以及和B相關聯的各邊後,圖若被分割成兩個或以上的圖,則稱V為關節點。

2)一個沒有關節點的連通圖,稱為重連通圖。

3)刪去k個節點後,才會破壞圖的連通性,則該圖的連通度為k。

2、獲取方式

圖的關鍵點數量可以用深度優先搜尋的方法獲取。

關節點主要有以下兩個特性:

1)若生成樹的根有兩個以上的子節點,則此根為關節點。

2)若生成樹的某個非葉子節點V,其若干棵子樹以及子樹的子節點均沒有指向V的祖先的回邊,則V即為關節點。

3)關節點至少要與兩個節點相連(如果只和一個節點相連,則是葉子節點,其是否斷開不影響圖的連通性)。

根據以上兩個特性,主要判斷方式如下:

1)資料結構利用上一篇將圖遍歷時候的節點結構

class Node{
         public $val = null;
         public $arrNext =array();//儲存下一個節點位置的陣列
}

2)遍歷每一個節點,對節點進行篩選,以下三種情況,V不是關節點,其餘V是關節點。

A. 當節點V的arrNext陣列小於或等於1時,V為葉子節點或為單獨的節點,則V不是關節點;

B. 如果V的某個子節點vi,是V其他子節點vk的子節點vx(不含V)的子節點,則V不是關節點;

C.如果V的某個子節點vi的子節點vk(不含V),是V其他子節點 vx(不含vk)的子節點,則V不是關節點。

否則,節點V是關節點。

三、最小生成樹

1、場景

現假設需要對一個城市的某幾個區域進行通訊網路的連線,每兩個點之間有自己的耗費,現需要把這幾個點連線起來行程通訊網,又想要最節省耗費。

將每個區域看成一個節點,區域之間看成無向圖的邊,每兩個點之間的耗費看成邊的權,則該問題化簡為求一個無向圖考慮到權值情況下的的最小生成樹。

2、概念

1)生成樹的代價:各邊權值的和,代價最小時稱為最小生成樹。

2)MST:最小生成樹的性質,假設連通網N=(V, {E}),U是頂點集V的一個非空子集,若(u, v)是一條具有最小權值的邊,其中u屬於U,v屬於V-U,則必存在一棵包含點(u, v)的最小生成樹。

3)最小生成樹有兩種演算法,一種叫做普里姆(Prim)演算法,一種叫做克魯斯卡爾(Kruskal)演算法。

(PS:本來寫文章時,是將Prim和Kruskal演算法放在一篇的,也便於比較,但是微信公眾號有個限制3000字的規定,所以我只能把Kruskal演算法挪到下一篇,見諒。)

3、Prim演算法

1)該演算法的時間複雜度為O(n2),即其時間複雜度和邊的數目無關,僅和頂點數目有關,適用於邊數較多的稠密網。

2)演算法內容

假設N={V, {E}}是連通網,TE是N上最小生成樹的集合。演算法從U={u0}(即圖上任意一點),TE={}開始,重複執行以下操作:

在所有的u屬於V,v屬於V-U的邊(u, v)屬於E,著一條代價最小的邊(u0,v0),併入集合TE,v0併入U,直到U=V為止。則TE包含n-1條邊,T=(V, {TE})是最小生成樹。

該演算法需要引入一個二維陣列,記錄任意兩個頂點之間的權值,如果兩個頂點沒有連線,則權值為無窮大。

4、Kruskal 挪至下一篇文章描述,原因見上述 斜體字。

5、總結

Prim演算法和Kruskal演算法,區別在於從頂點切入還是從邊切入。因此,當頂點較多但邊相對較少時,可以使用Kruskal演算法;反之,頂點較少而邊相對較多時,可以使用Prim演算法。兩個演算法都需要引入一個二維陣列,用於儲存任意兩點間的權值,當兩點沒有連線時,權值為無窮大,表示該點無法直接到達另一點。

6、編碼實現

PHP實現Prim演算法和Kruskal演算法的執行結果如下:

原始碼如下:

<?php
class MinTree{
         public$siteWeigh = array();
         publicfunction __construct($data = null){
                   if($data!= null){
                            $this->siteWeigh= $data;
                   }else{
                            //構造二維陣列,存放任意兩點間的權值
                            //由於是無向圖,因此具有軸對稱性
                            //用999表示兩個點沒有連線 
                            $this->siteWeigh= array(
                                     0=> array(999, 10, 15, 999, 20),
                                     1=> array(10, 999, 999, 10, 5),
                                     2=> array(15, 999, 999, 25, 999),
                                     3=> array(999, 10, 25, 999, 10),
                                     4=> array(20, 5, 999, 10, 999)
                            );
                   }
         }
         //Prim演算法:以頂點為依據生成最小生成樹
         publicfunction getPrimResult(){
                   $arrTree= $this->siteWeigh;
                   $allKeys= array_keys($arrTree);//獲取全部節點
                   $nodeNum= count($allKeys);
                   $nodeStack= array();//用於存放已經被納入結果集的節點
                   array_push($nodeStack,0);
                   $resStack= array();//用於存放結果路徑,格式0=>ij,1=>jk
                   $curNodeNum= count($nodeStack);
                   //Prim演算法中獲取所有節點後即完成演算法
                   while($curNodeNum< $nodeNum){
                            $curMin= 999;
                            $tmpArr= array();//暫存中間結果資訊
                            $k= 0;//暫存節點替換資訊,一輪內如果有新的權值更小的節點,則原節點出棧 
                            foreach($nodeStackas $curNode){//每次foreach從結果集中取一個點
                                     for($i=0;$i<$nodeNum;$i++){
                                               if(in_array($curNode,$nodeStack) && in_array($i, $nodeStack)){
                                                        continue;//如果兩個節點都已經在結果集,則進入下一輪迴圈
                                               }
                                               if($arrTree[$curNode][$i]< $curMin){                                         
                                                        if($k> 0){
                                                                 //nodestack是用於存 結果集的,因此如果再次進到這個if,
                                                                 //說明上次進結果集的是暫存的內容,需要清除
                                                                 array_pop($nodeStack);
                                                        }
                                                        $curMin= $arrTree[$curNode][$i];
                                                        $tmpArr[$curMin]= $curNode.$i;//拼接成ij的形式,便於確定是哪條邊
                                                        array_push($nodeStack,$i);
                                                        $k++;
                                               }
                                     }                                   
                            }
                            array_push($resStack,$tmpArr[$curMin]);
                            //計數確認當前是否已經捕獲所有節點
                            $curNodeNum= count($nodeStack);
                   }
                   //計算路徑值
                   $sum= 0;
                   foreach($resStackas $key => $val){
                            $i= intval($val[0]);
                            $j= intval($val[1]);
                            $sum+= $arrTree[$i][$j];
                   }
                   returnarray('resRoad' => $resStack, 'resSum' => $sum);
         }
 }

$minTree = new MinTree();
$primRes = $minTree->getPrimResult();
echo '採用Prim演算法,獲取的最小生成樹為:';
print_r($primRes['resRoad']);
echo '<br />最終的權值和為:'.$primRes['resSum'].'<br/>';

題外話:兩種最小生成樹演算法,Prim以節點為切入點獲取最小生成樹,Kruskal以邊為切入點獲取最小生成樹。兩者實現方式較為不同,Prim演算法主要以棧的思想進行解決,因此實際編碼過程中進出棧的處理邏輯需要理清楚;Kruskal重在排序,當每條邊的長度排好時,其他問題迎刃而解。

——written by linhxx 2017.07.09

相關閱讀:

PHP資料結構(十) ——有向無環圖與拓撲演算法

PHP資料結構(九) ——圖的定義、儲存與兩種方式遍歷

PHP資料結構(八) ——赫夫曼樹實現字串編解碼(實踐2)

PHP資料結構(八) ——赫夫曼樹實現字串編解碼(實踐1)

PHP資料結構(八) ——赫夫曼樹實現字串編解碼(理論)

PHP資料結構(七) ——串與實現KMP演算法

PHP資料結構(六) ——樹與二叉樹之概念及儲存結構

PHP資料結構(六) ——陣列的相乘、廣義表

PHP資料結構(五) ——陣列的壓縮與轉置

PHP資料結構(四) ——佇列

PHP資料結構(三)——運用棧實現括號匹配

PHP資料結構(二)——鏈式結構線性表

PHP資料結構(一)——順序結構線性表