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