1. 程式人生 > 其它 >PHP資料結構(九) ——圖的定義、儲存與兩種方式遍歷

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

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

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

一、定義和術語

1、不同於線性結構和樹,圖是任意兩個元素之間都可以有關聯的資料結構。

2、頂點:資料元素;弧:頂點A至頂點B的連線,弧是單向的,出發的點稱為弧尾,抵達的點稱為弧頭;邊:頂點A和B之間的連線,沒有方向性。

3、有向圖:由頂點和弧組成的圖;無向圖:由頂點和邊組成的圖。

4、完全有向圖:n個頂點有n(n-1)個弧;完全無向圖:n個頂點有n(n-1)/2個邊。

5、稀疏圖:邊或弧很少的圖(e<nlogn),反之為稠密圖。

6、權:弧或邊帶有的係數;網:帶權的圖。

7、子圖:圖的邊和頂點都被含於另一個圖,則該圖是另一個圖的子圖。

8、無向圖的鄰接點:兩個頂點A、B和其連線的邊x都屬於某個圖,則稱這兩個點A、B互為鄰接點,連線的邊x依附於這兩個鄰接點,A、B與x相關聯。

9、有向圖的鄰接點:兩個頂點A、B和弧x=A->B都屬於某個圖,則稱這兩個點A鄰接到B,B鄰接自A, A、B與x相關聯。

10、無向圖的度:與頂點V相關聯的邊的數目稱為V的度,記作TD(V)。

11、有向圖的度:頂點V作為弧尾的弧的數目稱為出度,記作OD(V);頂點V作為弧頭的弧的數目稱為入度,記作ID(V)。

12、路徑是指從頂點A經過若干邊或弧抵達頂點B,經過的邊或弧的數目稱為路徑的長度,起止點相同的路徑稱為迴路或環。頂點不重複的路徑稱為簡單路徑,起止點以外不重複的路徑稱為簡單迴路或簡單環。

13、無向圖與連通:兩個頂點之間有路徑,稱為兩個頂點連通;任意兩個頂點連通,稱為整個圖為連通圖;無向圖的極大連通子圖稱為連通分量。

14、有向圖與強連通:每一對(v,v’)(v不等於v’)都有路徑,稱為強連通圖。有向圖的極大連通子圖稱為強連通分量。

15、生成樹含義:生成樹是連通圖的極小連通子圖,包含圖的全部頂點,但是隻有n-1條邊。

16、有向樹含義:有向圖中,恰有一個頂點入度為0,其餘頂點入度為1。

17、生成森林:若干個數,含有圖的全部頂點,但是隻有足以構成若干不相交的樹的弧。

二、儲存結構

圖通常沒有順序儲存結構,但是可以藉助陣列(通常是二維陣列)進行儲存。因此,圖的儲存結構有:陣列表示法、鄰接表、鄰接多重表、十字連結串列等。

1、陣列表示法

從0開始,給每個頂點一個下標,用二位陣列arr[i][j](i、j屬於頂點)表示頂點i和頂點j的連通情況。連通時arr[i][j]=1(如果是帶權的,則連通時arr[i][j]=wij,即ij連線線的權值),不連通時arr[i][j]=0。

對於無向圖,陣列表示法表示的圖是一個對稱矩陣,可以僅存半個矩陣節約空間。

2、鄰接表

鄰接表採用連結串列結構,每條邊或弧有三個儲存空間,分別表示第一個節點、邊的權值、下一個節點的位置。因此,對於有向圖,鄰接表是出度表。如果需要入度表,則稱作逆鄰接表。

有向圖和無向圖鄰接表示例圖如下:

3、十字連結串列

十字連結串列是針對有向圖的一種儲存方式,其結合了有向圖的鄰接表和逆鄰接表,在鄰接表的基礎上,加一個欄位,用於儲存以此節點作為弧頭的位置。則每個節點都可以追溯到其下一個節點,也可以找回其前一個節點。

4、鄰接多重表

鄰接多重表是針對無向圖的一種儲存方式。使用此儲存方式,主要是改進無向圖鄰接表儲存時的一個缺點——改動其中任一內容,需要同時改動對應的另一個內容,因為在無向圖中邊ab和ba是一樣的,改動ab的內容,要同步改動ba的內容。鄰接多重表,即對於一條邊,僅用一個儲存結構進行儲存,不區分ab或者ba的方式。

三、圖的遍歷

1、概念

1)圖的遍歷,表示從圖的某一個頂點出發,訪問每個節點有且僅有一次。

2)為了避免重複反問,需要記錄已經訪問過的節點。

3)有兩種方式進行遍歷,深度優先搜尋和廣度優先搜尋。

2、深度優先搜尋

深度優先搜尋,運用到棧的概念,當多個點和一個點成線時,先遍歷一個節點,並優先遍歷其子節點,直至確認沒有子節點,才遍歷點的下一個節點。

3、廣度優先搜尋

廣度優先搜尋,運用到佇列的概念,遍歷一個點時,先遍歷其每一個節點,再按照第一次遍歷的順序,遍歷每個節點的子節點。

4、範例

如下圖所示。

PHP程式碼執行結果如下:

程式碼核心步驟:

1、根據指定的輸入方式,把各節點的關係生成圖。

2、深度優先演算法:採用棧(後進先出LIFO)的思想,遍歷節點時,被遍歷的節點出棧,再遍歷其子節點,將子節點逐一進棧。需要注意的是,為了防止重複遍歷,被遍歷過的節點以及已經進棧的節點,需要用一個數組存著,避免再次進站。

3、廣度優先演算法:採用佇列(先進先出FIFO)的思想,遍歷節點時,被遍歷的節點出佇列,再遍歷其子節點。關鍵要點和深度優先演算法類似。

PHP原始碼如下:

<?php
//實現連通圖的深度、廣度優先搜尋
class Node{
         public$val = null;
         public$arrNext = array();//儲存下一個節點位置的陣列
         publicfunction __construct($val = null){
                   $this->val= $val;
         }
}
class Graph{
         //建立連通圖,nodes =array('val1'=>array('val2','val3'..),'val2'=>array(...));
         //返回第一個節點,由於是連通圖,可以根據第一個節點遍歷整個圖
         publicfunction generate(array $nodes){
                   $arrNodes= array_keys($nodes);
                   $resNodes= array();
                   foreach($arrNodesas $nodeVal){
                            $resNodes[$nodeVal]= new Node($nodeVal);
                   }
                   foreach($nodesas $key => $val){
                            foreach($valas $node){
                                     if(isset($resNodes[$node]) &&is_object($resNodes[$node])){
                                               $resNodes[$key]->arrNext[]= $resNodes[$node];
                                     }
                            }
                   }
                   returncurrent($resNodes);
         }
         //深度優先搜尋
         publicfunction searchByDeep(Node $node){
                   $resultWord= array();//存放遍歷結果
                   $nodeStack= array();//存放遍歷過程中的中間結果
                   $wordStack= array();//**存放當前已經進棧的節點,防止重複進棧**
                   array_push($nodeStack,$node);
                   array_push($wordStack,$node->val);
                   while(!empty($nodeStack)){
                            $curNode= array_pop($nodeStack);
                            array_push($resultWord,$curNode->val);//節點進棧
                            $arrNext= $curNode->arrNext;
                            //遍歷節點的next陣列,找出所有的子節點
                            for($i=count($arrNext)-1;$i>=0;$i--){
                                     $curChildNode= $arrNext[$i];
                                     //判斷節點不在結果集且不在棧內,則進棧,避免重複
                                     if(!in_array($curChildNode->val,$resultWord) && !in_array($curChildNode->val, $wordStack)){
                                               array_push($nodeStack,$curChildNode);
                                               array_push($wordStack,$curChildNode->val);//標記該節點已經進棧
                                     }
                            }
                   }
                   return$resultWord;
         }
         //廣度優先搜尋
         publicfunction searchByWide(Node $node){
             $resultWord= array();//存放遍歷結果
             $nodeStack= array();//存放遍歷過程中的中間結果
             $wordStack= array();//**存放已經遍歷過子節點的節點,防止重複遍歷**
             array_push($nodeStack,$node);
             while(!empty($nodeStack)){
                      $curNode= array_shift($nodeStack);//第一個節點出棧,實現佇列
                      array_push($resultWord,$curNode->val);//節點進棧
                      array_push($wordStack,$curNode->val);//即將開始遍歷其子節點
                       $arrNext= $curNode->arrNext;
                       //遍歷節點的next陣列,找出所有的子節點
                       for($i=0;$i<count($arrNext);$i++){
                            $curChildNode= $arrNext[$i];
                             //判斷節點不在結果集且不在棧內,則進棧,避免重複
                         if(!in_array($curChildNode->val,$resultWord) && !in_array($curChildNode->val, $wordStack)){
                      array_push($nodeStack,$curChildNode);
                      array_push($wordStack,$curChildNode->val);//標記該節點已經進棧
                                     }                                   
                            }                          
                   }
                   return$resultWord;
         }
}
$graph = new Graph();
$arrNode = array(
         'a'=> array('b', 'f'),
         'b'=> array('a', 'c', 'd'),
         'c'=> array('b', 'd', 'e'),
         'd'=> array('b', 'c'),
         'e'=> array('c'),
         'f'=> array('a', 'g', 'h'),
         'g'=> array('f', 'h'),
         'h'=> array('f', 'g')
);
$graphTree = $graph->generate($arrNode);
$res = $graph->searchByDeep($graphTree);
echo '<br />深度優先搜尋的結果是:';
print_r($res);
echo '<br />廣度優先搜尋的結果是:';
$res = $graph->searchByWide($graphTree);
print_r($res);

——written by linhxx 2017.07.08

相關閱讀:

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

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

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

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

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

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

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

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

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

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

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