BFS and DFS

First Post:

Last Update:

Word Count:
1.2k

Read Time:
5 min

BFS广度优先遍历和DFS深度优先遍历

广度优先搜索(Breadth First Search,简称BFS)

假设从A节点出发,在访问了该节点之后依次访问这个节点各个未曾访问过的邻接点,然后分别从这些邻接点出发依次访问它们的邻接点。重复此步骤,直到所有的节点都被访问完为止。

如上图,广度优先搜索的顺序是:ABCDEFG

深度优先搜索(Depth First Search,简称DFS)

假设从A节点出发,首先访问该节点,然后依次从它的各个未被访问的邻接点出发,访问邻接点的邻接点。重复此步骤,直到所有的节点都被访问完为止。

如上图,深度优先搜索的顺序是:ABDECFG

实战

现在以一个实际的例子用C#代码来实现,下面是Unity中的一个Hierarchy层级图,根节点是Root,下面有GameObject,GameObject (1), GameObject (2)三个节点,其中GameObject (1)下又有GameObject_A和GameObject_B两个节点:

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
using System.Collections.Generic;
using UnityEngine;

public class DM06Composite : MonoBehaviour
{
void Start()
{
DMComposite root = new DMComposite("Root");
// 添加Root下的三个节点
DMLeaf leaf1 = new DMLeaf("GameObject");
DMLeaf leaf2 = new DMLeaf("GameObject (2)");
DMComposite gameObject1 = new DMComposite("GameObject (1)");
root.AddChild(leaf1);
root.AddChild(gameObject1);
root.AddChild(leaf2);

// 添加GameObject (1)下面的两个节点
DMLeaf child1 = new DMLeaf("GameObject_A");
DMLeaf child2 = new DMLeaf("GameObject_B");
gameObject1.AddChild(child1);
gameObject1.AddChild(child2);

// 按照广度优先或深度优先输出节点顺序
//DepthFirstSearch(root);
BreadthFirstSearch(root);
}

// 广度优先检索
private void BreadthFirstSearch(DMComponent component)
{
Queue<DMComponent> q = new Queue<DMComponent>();
q.Enqueue(component);
// 打印节点名字
Debug.Log(component.Name);
while (q.Count > 0)
{
DMComponent temp = q.Dequeue();
List<DMComponent> children = temp.Children;
foreach (DMComponent child in children)
{
// 打印节点名字
Debug.Log(child.Name);
q.Enqueue(child);
}
}
}

// 深度优先检索
private void DepthFirstSearch(DMComponent component)
{
// 打印节点名字
Debug.Log(component.Name);
List<DMComponent> children = component.Children;
if (children == null || children.Count == 0) return;
foreach (DMComponent child in children)
{
DepthFirstSearch(child);
}
}
}

// 抽象组件类
public abstract class DMComponent
{
protected string mName;
public string Name { get { return mName; } }
public DMComponent(string name)
{
mName = name;
mChildren = new List<DMComponent>();
}
protected List<DMComponent> mChildren;
public List<DMComponent> Children { get { return mChildren; } }
public abstract void AddChild(DMComponent c);
public abstract void RemoveChild(DMComponent c);
public abstract DMComponent GetChild(int index);
}

// 叶子类,一棵树当中没有子节点的节点
public class DMLeaf : DMComponent
{
public DMLeaf(string name) : base(name) { }


public override void AddChild(DMComponent c)
{
return;
}

public override void RemoveChild(DMComponent c)
{
return;
}

public override DMComponent GetChild(int index)
{
return null;
}
}

// 容器类,可以有子节点
public class DMComposite : DMComponent
{
public DMComposite(string name) : base(name) { }


public override void AddChild(DMComponent c)
{
mChildren.Add(c);
}

public override void RemoveChild(DMComponent c)
{
mChildren.Remove(c);
}

public override DMComponent GetChild(int index)
{
return mChildren[index];
}
}

广度优先搜索:

img

深度优先搜索:

img

解析

上面的代码稍微有点复杂,下面详细的解释一下,首先,使用设计模式中的组合模式:组合多个对象形成树形结构,以表示具有“整体-部分”关系的层次结构。组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性。

抽象组件类(Component):它可以是接口或抽象类,为叶子组件和容器组件对象声明接口,在该角色中可以包含所有子类共有行为的声明和实现。在抽象组件类中,定义了访问及管理它的子组件的方法;

叶子类(Leaf):在组合模式中,表示叶子节点对象,叶子节点没有子节点,实现了在抽象组件类中定义的行为。

容器类(Composite):在组合模式中,表示容器节点对象,容器节点包含子节点,其子节点可以叶子节点,也可以是容器节点,提供了一个集合用于存储子节点,实现了在抽象组件类中定义的行为,包括访问及管理子组件的方法。

使用组合模式的目的是为了组装上面的层次结构,建立好层次结构后,就可以进行检索了。

广度优先搜索实现的核心思想:

建立一个队列Queue,利用它的先进先出原则,把根节点放入队列中,在一个while循环中,检索根节点的所有子节点,把根节点从队列中移除,然后把子节点放入队列中,检索子节点的所有子节点,依次循环执行。

深度优先搜索实现的核心思想:

首先遍历根节点的所有子节点,然后使用递归,依次遍历子节点下面的子节点。

ref:

https://zhuanlan.zhihu.com/p/349093576

打赏点小钱
支付宝 | Alipay
微信 | WeChat