一、定義
深度優(yōu)先遍歷(Depth-First Search,DFS)和廣度優(yōu)先遍歷(Breadth-First Search,BFS)是兩種主要的圖或樹結構的遍歷算法。
DFS優(yōu)先深入地探索一個節(jié)點的子節(jié)點,直到該節(jié)點的所有子節(jié)點都已被探索完,然后再回溯到該節(jié)點的同級節(jié)點進行探索;
BFS則優(yōu)先探索一個節(jié)點的所有同級節(jié)點,再逐級向下探索。
在前端的工作中,如果遇到樹形 DOM 結構、樹型控件、級聯(lián)選擇等等需求,都需要使用到DFS和BFS。
二、算法步驟
2.1 樹
樹是一種分層數(shù)據(jù)的抽象模型,樹可以看做是一種特殊的鏈表,只是鏈表只有一個 next 指向下一個節(jié)點,而樹的每個節(jié)點都有多個 next 指向下一個節(jié)點。
一個樹結構包含一系列存在父子關系的節(jié)點。每個節(jié)點都有一個父節(jié)點(除了頂部的第一個節(jié)點)以及零個或多個子節(jié)點:
JavaScript 中沒有樹這種數(shù)據(jù)結構,但是可以用 Object 和 Array 來模擬一顆樹。
const tree = {
value:"a",
children:[
{
value:"b",
children:[
{
value:"d",
children:[
{
value:"e",
children:[]
}
]
}
]
},
{
value:"c",
children:[
{
value:"f",
children:[]
},
{
value:"g",
children:[]
}
]
}
]
}
var data = [
{
name: "a",
children: [
{
name: "b",
children: [
{
name: "e",
},
],
},
{
name: "c",
children: [
{
name: "f",
},
],
},
{
name: "d",
children: [
{
name: "g",
},
],
},
],
},
{
name: "a2",
children: [
{
name: "b2",
children: [
{
name: "e2",
},
],
},
{
name: "c2",
children: [
{
name: "f2",
},
],
},
{
name: "d2",
children: [
{
name: "g2",
},
],
},
],
},
];
2.2 深度優(yōu)先遍歷(DFS)
深度優(yōu)先遍歷,盡可能深的搜索樹的分支。
序號表示被搜索的順序,它的算法口訣是:
訪問根節(jié)點;
對根節(jié)點的 children 挨個(遞歸)進行深度優(yōu)先遍歷。
# tree 為上述的結構
# 深度優(yōu)先代碼
const dfs = (node)=>{
console.log(node.value);
node.children.forEach(dfs);
}
# 調(diào)用
dfs(tree);
打印結果輸出順序: a、b、d、e、c、f、g 。
2.3 廣度優(yōu)先遍歷(BFS)
廣度優(yōu)先遍歷,先訪問離根節(jié)點最近的節(jié)點。
序號表示被搜索的順序,先把同層的節(jié)點給遍歷完,再去遍歷子節(jié)點。它的算法口訣是:
新建一個隊列,把根節(jié)點入隊;
把對頭出隊并訪問;
把對頭的 children 挨個入隊;
重復(循環(huán))第二、三步,直到隊列為空。
const bfs = (root)=>{
# 根節(jié)點入隊
const stack = [root];
# 只要棧不為空就一直循環(huán)
while (stack.length > 0){
# 取出棧首
const node = stack.shift();
# 遍歷根節(jié)點,把它的子節(jié)點推入棧尾
node.children.forEach((item)=> stack.push(item));
# 打印節(jié)點值
console.log(node.value);
}
}
bfs(tree);
打印結果輸出順序: a、b、c、d、e、f、g 。
三、使用場景 & 應用案例
3.1 深度優(yōu)先遍歷
● 在圖或樹的復雜結構中搜索特定節(jié)點。
● 用于解決如迷宮搜索、尋找連通性組件等問題。
● 查找文件路徑
● 二叉樹的遍歷
3.2 廣度優(yōu)先遍歷
● 尋找最短路徑,如無權重圖的最近節(jié)點或社交網(wǎng)絡中的度數(shù)。
● 逐層遍歷,如層級結構的打印。
● 權限系統(tǒng)
● Web 爬蟲(廣度優(yōu)先搜索也被應用在互聯(lián)網(wǎng)搜索引擎的網(wǎng)頁爬蟲技術中,以盡可能廣泛地爬取頁面。)
四、優(yōu)點和缺點
4.1深度優(yōu)先遍歷 (DFS):
優(yōu)點:
- 路徑檢測:DFS 非常適合搜索所有可能的路徑,因為它走的“更深”。
- 內(nèi)存較少:相比 BFS,DFS 使用的內(nèi)存較少。因為它只需要存儲單條路徑上的節(jié)點。
缺點:
- 時間較多:在某些情況下,尤其是在目標節(jié)點離初始節(jié)點較近時,或者在解決最短路徑問題時, DFS 可能需要花費不必要的更多時間。
- 無限循環(huán):在非樹形圖結構中,由于 DFS 偏向深入,可能遇到不斷深入但找不到解的歷史循環(huán)問 題。
深度優(yōu)先遍歷的優(yōu)點在于能快速找到解決方案,且易于實現(xiàn),但有可能陷入死循環(huán)或者掉入一些非最優(yōu)的情況。
4.2廣度優(yōu)先遍歷 (BFS):
優(yōu)點:
- 最短路徑:在樹形或圖形結構中,BFS 可以找到從根節(jié)點到目標節(jié)點的最短路徑。
- 層級遍歷:由于 BFS 是逐層遍歷,因此很適用于需要按層級遍歷的場合。
缺點:
- 內(nèi)存:相比 DFS,BFS 使用的內(nèi)存較多。尤其是在樹的分支很多時,因為它需要存儲整個擴展的節(jié) 點隊列。
- 路徑檢測:在需要找到所有可能的路徑時,BFS效率較低。
廣度優(yōu)先遍歷可以找到最優(yōu)解,特別是在需要找到最短路徑的問題中,但可能需要更多的存儲空間。
五、時間和空間復雜度
5.1 BFS(廣度優(yōu)先搜索)
- 時間復雜度:是O(V+E),其中V是圖中頂點(Vertex)的數(shù)量,E是圖中邊(Edge)的數(shù)量。這是因為在算法執(zhí)行過程中,每個頂點和每條邊都會被探查一次。
- 空間復雜度:是O(V),其中V是圖中的頂點的數(shù)量。在最壞的情況下,即在隊列中存放了圖的所有頂點,所以空間復雜度與頂點的數(shù)量有關。
5.2 DFS(深度優(yōu)先搜索)
- 時間復雜度:是O(V+E),其中V是圖中頂點(Vertex)的數(shù)量,E是圖中邊(Edge)的數(shù)量。與BFS一樣,這是因為在算法執(zhí)行過程中,每個頂點和每條邊都會被訪問一次。
- 空間復雜度:是O(V),其中V是圖中的頂點的數(shù)量。在最壞的情況下,即在調(diào)用棧中存放了圖的所有頂點,所以空間復雜度與頂點的數(shù)量有關。
注意:以上的復雜度分析都是基于鄰接列表的圖數(shù)據(jù)結構進行的。如果使用鄰接矩陣,由于需要遍歷每個點對應的所有邊,因此BFS和DFS的時間復雜度會變?yōu)镺(V^2)。
所以,當選擇圖的數(shù)據(jù)結構時(例如,鄰接矩陣還是鄰接列表),需要考慮到實際應用的特點,如圖的稀疏或密集程度,以選擇最優(yōu)的數(shù)據(jù)結構,從而提高程序的效率。
六、 總結
深度優(yōu)先遍歷和廣度優(yōu)先遍歷是兩種基本且重要的圖與樹的遍歷算法,選擇使用哪一種遍歷方法取決于問題的具體需求,理解它們的運作原理及適用場景是所有算法學習基礎。