圖的深度優先遍歷和廣度優先遍歷


深度優先遍歷簡稱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();

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM