深度優先遍歷簡稱DFS(Depth First Search),廣度優先遍歷簡稱BFS(Breadth First Search),它們是遍歷圖當中所有頂點的兩種方式。
我們來到一個游樂場,游樂場里有11個景點。我們從景點0開始,要玩遍游樂場的所有景點,可以有什么樣的游玩次序呢?
深度優先遍歷
二叉樹的前序、中序、后序遍歷,本質上也可以認為是深度優先遍歷。
第一種是一頭扎到底的玩法。我們選擇一條支路,盡可能不斷地深入,如果遇到死路就往回退,回退過程中如果遇到沒探索過的支路,就進入該支路繼續深入。
在圖中,我們首先選擇景點1的這條路,繼續深入到景點4、景點5、景點3、景點6,終於發現走不動了(景點旁邊的數字代表探索次序):
於是,我們退回到景點1,然后探索景點7,景點8,又走到了死胡同。於是,退回到景點7,探索景點10:
按照這個思路,我們再退回到景點1,探索景點9,最后再退回到景點0,后續依次探索景點2,終於玩遍了整個游樂場:
廣度優先遍歷
二叉樹的層序遍歷,本質上也可以認為是深度優先遍歷。
在圖中,我們首先探索景點0的相鄰景點1、2、3、4
接着,我們探索與景點0相隔一層的景點7、9、5、6:
最后,我們探索與景點0相隔兩層的景點8、10:
<?php /** * 圖的深度優先遍歷、廣度優先遍歷 * 圖的存儲結構--鄰接矩陣 */ class Graph { // 存儲節點信息 public $vertices; // 存儲邊信息 public $arcs; // 圖的節點數 public $vexnum; // 記錄節點是否已被遍歷 public $visited = []; // 初始化 public function __construct($vertices) { $this->vertices = $vertices; $this->vexnum = count($this->vertices); for ($i = 0; $i < $this->vexnum; $i++) { for ($j = 0; $j < $this->vexnum; $j++) { $this->arcs[$i][$j] = 0; } } } // 兩個頂點間添加邊(無向圖) public function addEdge($a, $b) { if ($a == $b) { // 邊的頭尾不能為同一節點 return; } $this->arcs[$a][$b] = 1; $this->arcs[$b][$a] = 1; } // 從第i個節點開始深度優先遍歷 public function traverse($i) { // 標記第i個節點已遍歷 $this->visited[$i] = 1; // 打印當前遍歷的節點 echo $this->vertices[$i] . PHP_EOL; // 遍歷鄰接矩陣中第i個節點的直接聯通關系 for ($j = 0; $j < $this->vexnum ; $j++) { // 目標節點與當前節點直接聯通,並且該節點還沒有被訪問,遞歸 if ($this->arcs[$i][$j] == 1 && $this->visited[$j] == 0) { $this->traverse($j); } } } //深度優先遍歷 public function dfs() { // 初始化節點遍歷標記 $this->init(); // 從沒有被遍歷的節點開始深度遍歷 for ($i = 0; $i < $this->vexnum; $i++) { if ($this->visited[$i] == 0) { // 若是連通圖,只會執行一次 $this->traverse($i); } } } // 初始化節點遍歷標記 public function init(){ for ($i = 0; $i < $this->vexnum; $i++) { $this->visited[$i] = 0; } } //廣度優先遍歷 public function bfs() { // 初始化節點遍歷標記 $this->init(); $queue = []; for ($i = 0; $i < $this->vexnum; $i++) { // 對每一個頂點做循環 if (!$this->visited[$i]) { // 若是未訪問過就處理 $this->visited[$i] = 1; // 設置當前頂點訪問過 echo $this->vertices[$i] . PHP_EOL; // 打印頂點 $queue[] = $i; // 將此頂點入隊列 while (!empty($queue)) { // 若當前隊列不為空 $curr = array_shift($queue); // 將隊對元素出隊 for ($j = 0; $j < $this->vexnum; $j++) { if ($this->arcs[$curr][$j] == 1 && $this->visited[$j] == 0) { $this->visited[$j] = 1; // 將找到的此頂點標記為已訪問 echo $this->vertices[$j] . PHP_EOL; // 打印頂點 $queue[] = $j; // 將找到的此頂點入隊列 } } } } } } } /* 0 1 2 3 4 5 6 7 8 9 10 0 0 1 1 1 1 0 0 0 0 0 0 1 1 0 0 0 1 0 0 1 0 1 0 2 1 0 0 0 0 0 0 0 0 0 0 3 1 0 0 0 0 1 1 0 0 0 0 4 1 1 0 0 0 1 0 0 0 0 0 5 0 0 0 1 1 0 0 0 0 0 0 6 0 0 0 1 0 0 0 0 0 0 0 7 0 1 0 0 0 0 0 0 1 0 1 8 0 0 0 0 0 0 0 1 0 0 0 9 0 1 0 0 0 0 0 0 0 0 0 10 0 0 0 0 0 0 0 1 0 0 0 so 0 1,2,3,4 1 0,4,7,9 2 0 3 0,5,6 4 0,1,5 5 3,4 6 3 7 1,8,10 8 7 9 1 10 7 */ // 測試 $vertices = ['景點0', '景點1', '景點2', '景點3', '景點4', '景點5', '景點6', '景點7', '景點8', '景點9', '景點10']; $graph = new Graph($vertices); $graph->addEdge(0, 1); $graph->addEdge(0, 2); $graph->addEdge(0, 3); $graph->addEdge(0, 4); $graph->addEdge(1, 4); $graph->addEdge(1, 7); $graph->addEdge(1, 9); $graph->addEdge(3, 5); $graph->addEdge(3, 6); $graph->addEdge(4, 5); $graph->addEdge(7, 8); $graph->addEdge(7, 10); // 遞歸 echo "dfs:"; $graph->dfs(); echo "<br />"; echo "bfs:"; $graph->bfs();