开启生长之旅!这是我参加「日新方案 12 月更文挑战」的第20天,点击查看活动概况。
一,什么是查找算法
算法是基于特定数据结构之上的,深度优先查找算法和广度优先查找算法都是基于“图”这种数据结构的。
树是图的一种特例(连通无环的图便是树)。
图上的查找算法,最直接的了解便是,在图中找出从一个极点动身,到另一个极点的途径。具体方法有许多,两种最简单、最“暴力”的深度优先、广度优先查找,还有 A*
、IDA*
等启发式查找算法。深度优先查找算法和广度优先查找算法,既能够用在无向图,也能够用在有向图上。
图(选用邻接表存储)的 C++
代码完结如下:
#include <list>
// 无向图结构的定义
class Graph{
private:
int v; // 极点个数
list<int> adj() // 存储的邻接表
public:
// 结构函数定义
Graph(int v){
adj = new List<int>(v);
for (int i = 0; i < v; i++){
adj[i] = new list<int> ();
}
}
// 无向图一条边存储 2 次
void addEdge(int s, int t){
adj[s].push_back(t);
adj[t].push_back(s);
}
}
二,广度优先查找(BFS)
广度优先查找(Breadth-First-Search),咱们往常都简称 BFS
。直观地讲,它其实便是一种“地毯式”层层推动的查找战略,即先查找离开始极点最近的,然后是次近的,依次往外查找。
求图中开始极点 s
到停止极点 t
的最短途径,可使用 bfs
算法,代码如下:
#include <queue>
using namespace std;
void bfs(int s, int t){
if( s==t ) return;
bool visited[v]; // 用来记载现已拜访过的极点, v 表明极点个数
queue<int> queue; // 一个行列,用来存储现已被拜访、但相连的极点还没有被拜访的极点。
queue.push_back(s); // 添加开始极点
vector<int> prev(v, -1); // prev 用来记载查找途径, prev[w]存储的是,极点 w 是从哪个前驱极点遍历过来的。
while(queue.size() != 0){
int w = queue.pop();
for(int i= 0; i<adj[w].size();++i){
int q = adj[w].at(i);
if(!visited[q]){
prev[q] = w;
if(q==t){
print_map(prev, s, t);
return;
}
visited[q] = true;
queue.add(q);
}
}
}
void print_map(int prev[], int s, int t){
if(prev[t] != -1 && t!=s){
print_map(prev, s, prev[t]);
}
cout << t << "+ ";
}
queue 是一个行列,用来存储现已被拜访、但相连的极点还没有被拜访的极点。因为广度优先查找是逐层拜访的,也便是说,咱们只要把第 k 层的极点都拜访完结之后,才干拜访第 k+1 层的极点。当咱们拜访到第 k 层的极点的时分,咱们需求把第 k 层的极点记载下来,稍后才干经过第 k 层的极点来找第 k+1 层的极点。所以,咱们用这个行列来完结记载的功用。
prev 用来记载查找途径。当咱们从极点 s 开始,广度优先查找到极点 t 后,prev 数组中存储的便是查找的途径。不过,这个途径是反向存储的。prev[w]存储的是,极点 w 是从哪个前驱极点遍历过来的。比如,咱们经过极点 2 的邻接表拜访到极点 3,那 prev[3]就等于 2。为了正向打印出途径,咱们需求递归地来打印。
最坏情况下,停止极点 t
离开始极点 s
很远,需求遍历完整个图才干找到。这个时分,每个极点都要进出一遍行列,每个边也都会被拜访一次,所以,广度优先查找的时刻复杂度是 O(V+E)O(V+E),其中,V
表明极点的个数,E
表明边的个数。当然,关于一个连通图来说,也便是说一个图中的一切极点都是连通的,E
肯定要大于等于 V-1
,所以,广度优先查找的时刻复杂度也能够简写为 O(E)O(E)。
广度优先查找的空间消耗主要在几个辅佐变量 visited
数组、queue
行列、prev
数组上。这三个存储空间的大小都不会超越极点的个数,所以空间复杂度是 O(V)O(V)。
三,深度优先查找(DFS)
深度优先查找(Depth-First-Search),简称 DFS
。
最直观的比如便是“走迷宫”。假定你站在迷宫的某个岔路口,然后想找到出口。你随意挑选一个岔路口来走,走着走着发现走不通的时分,你就回退到上一个岔路口,重新挑选一条路继续走,直到终究找到出口。这种走法便是一种深度优先查找战略。
bool found = false; // 类成员变量
void dfs(int s, int t){
found = false;
bool visited[v]; // v 表明极点个数
int prev[v];
for(int i=0; i< v;i++){
prev[i] = -1;
}
recurDFS(s, t, visited, prev);
print_map(prev, s, t)
}
void recurDFS(int w, int t, bool visited[], int prev[]){
if(found == true) return;
visited[w] = true;
if(w == t){
found = true;
return;
}
for(int i = 0; i < adj[w].size();++i){
int q = adj[w].at(i);
if(!visited[q]){
prev[q] = w;
recurDFS(q, t, visited, prev);
}
}
}
四,内容总结
广度优先查找,通俗的了解便是,地毯式层层推动,从开始极点开始,依次往外遍历。广度优先查找需求凭借行列来完结,遍历得到的途径便是,开始极点到停止极点的最短途径。深度优先查找用的是回溯思维,十分适合用递归完结。换种说法,深度优先查找是凭借栈来完结的。在履行效率方面,深度优先和广度优先查找的时刻复杂度都是 O(E)O(E),空间复杂度是 O(V)O(V)。
参考资料
《数据结构与算法之美》-深度和广度优先查找