【算法】第二章-搜索

搜索

深度优先搜索(DFS)

  深度优先搜索是使用递归的方式以深度为主逐个探索遍历每种情况,在排列组合,迷宫问题中十分常用。深度优先搜索思想简单,但是由于使用递归,要求我们遍历时探索的必须深度有限。不然有可能会使栈溢出。还要注意有时我们在使用深度优先搜索时情况过多,而大部分是无用解时就需要套入剪枝
  模型:

1
2
3
4
5
6
DFS()
{
//1.判断边界,如果已经到达搜索的最深,则回退尝试其他可能
//2.尝试当下的每一种可能
//3.确定一种可能后,继续下一步
}

例1 员工的重要度

  力扣:
https://leetcode-cn.com/problems/employee-importance/submissions/

解析

  抽象模型。

1
2
3
4
5
6
DFS(id)
{
//1.获取当前员工的重要度
//2.累加每一个下属(for)的重要度,DFS(下属id)
//3.return累加的重要度
}

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public:
int DFS(unordered_map<int, Employee*> em, int id)
{
int curRet = em[id]->importance;
for(auto& e : em[id]->subordinates)
{
curRet += DFS(em, e);
}
return curRet;
}
int getImportance(vector<Employee*> employees, int id) {
if(employees.empty())
{
return 0;
}
unordered_map<int, Employee*> em;
for(auto& e: employees)
{
em[e->id] = e;
}
return DFS(em, id);
}
};

例2 图像渲染

  力扣:
https://leetcode-cn.com/problems/flood-fill/

解析

  抽象模型。

1
2
3
4
5
6
7
8
9
10
11
DFS()
{
//1.nx,ny染色
//2.处理上,下,左,右4个点
{
//以深度优先逐个方向遍历周边4个点
//新位置颜色符合,且没有越界,且没有染过色
//符合条件处理新位置
DFS(新的位置)
}
}

实现

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
static int nextP[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
class Solution {
public:
void DFS(vector<vector<int>>& image, int row, int col, int nx, int ny, vector<vector<int>> book, int newColor, int oldColor)
{
image[nx][ny] = newColor;
book[nx][ny] = 1;
//上下左右遍历
for(int i = 0; i < 4; i++)
{
int newx = nx + nextP[i][0];
int newy = ny + nextP[i][1];
if(newx >= row || newx < 0 || newy >= col || newy < 0)
{
continue;
}
if(image[newx][newy] == oldColor && book[newx][newy] == 0)
{
DFS(image, row, col, newx, newy, book, newColor, oldColor);
}
}
}
vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int newColor) {
if(image.empty())
{
return image;
}
int row = image.size();
int col = image[0].size();
int oldColor = image[sr][sc];
vector<vector<int>> book(row, vector<int>(col, 0));
DFS(image, row, col, sr, sc, book, newColor, oldColor);
return image;
}
};

例3 走迷宫

  牛客:
https://www.nowcoder.com/questionTerminal/6276dbbda7094978b0e9ebb183ba37b9

解析

  模型:

1
2
3
4
5
6
DFS()
{
//1.当前位置已走,路径长度+1,并且置为墙表示走过了
//2.走到终点,和最短路径长度比较取最优
//3.遍历四周,如果不是墙且不越界则遍历
}

  但是,这种模型在一张只有极少障碍物的迷宫中要消耗很多的时间,因为我们要让每一个结点都被遍历近10次,这其中会消耗大量时间,并且很多是无用的解。因此我们在其中可以加入动态规划的思想。
  模型:

1
2
3
4
5
DFS()
{
//1.遍历四周,如果不是墙且不越界则进行二次判断
//2.如果本次路径到达这个位置的路径数要小于以往在表中记录的路径数,则将表中最优解进行修改,否则不再遍历此位置。
}

实现

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
#include <iostream>
#include <string>
#include <vector>
#include <string.h>
//这类迷宫问题不能纯粹使用深度优先搜索,因为要完全遍历一遍很慢,还要加上动态规划算法(剪枝)
//记录到达每个点上的最少步数,如果大于这个步数则不再走这个点,可以剪掉大量冗余无效的走法
using namespace std;
int res[10][10] = { 0 };
vector<string> mess(10, string(10, 0));
int nextP[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
void dfs(int i, int j)
{
//继续向四周遍历
for(int m = 0; m < 4; m++)
{
int newi = i + nextP[m][0];
int newj = j + nextP[m][1];
if(newi < 10 && newi >= 0 && newj < 10 && newj >= 0 && mess[newi][newj] != '#')
{
if(res[newi][newj] == 0 || res[newi][newj] > res[i][j] + 1)
{
res[newi][newj] = res[i][j] + 1;
dfs(newi, newj);
}
}
}
}
int main()
{
while(cin >> mess[0])
{
for(int i = 1; i < 10; i++)
{
cin >> mess[i];
}
dfs(0, 1);
cout << res[9][8] << endl;
memset(res, 0, sizeof(res));
}
}

广度优先搜索(BFS)

  广度优先搜索是以广度为主逐层往外一次遍历,不同于深度优先逐条解进行排列组合,广度优先搜索只会遍历一次所有结点,并且会最先拿到最优解,因此某些情况下广度优先搜索效率优于深度优先搜索。广度优先搜索在迷宫问题的解上效率会更高,在寻找最优解时会更快,因为其不需要列出所有组合的情况。
  广度优先搜索在实现时需要借助队列,每一次将下一层要遍历的结点入队,上一层出队。

例1 员工的重要性

  力扣:
https://leetcode-cn.com/problems/employee-importance/

解析

  将要查找的第一个员工入队,之后每次遍历队中元素将其重要度相加,并且将其下属入队。

实现

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
/*
// Employee info
class Employee {
public:
// It's the unique ID of each node.
// unique id of this employee
int id;
// the importance value of this employee
int importance;
// the id of direct subordinates
vector<int> subordinates;
};
*/
class Solution {
public:
int getImportance(vector<Employee*> employees, int id) {
unordered_map<int, Employee*> info;
for(auto& e : employees)
{
info[e->id] = e;
}
queue<int> q;
//将这个员工入队
q.push(id);
int ret = 0;
//广度优先遍历
while(!q.empty())
{
//依次加上队中所有员工的重要度
int curId = q.front();
q.pop();
ret += info[curId]->importance;
//将其下属入队
for(auto& e : info[curId]->subordinates)
{
q.push(e);
}
}
return ret;
}
};

例2 N叉树层序遍历

  力扣:
https://leetcode-cn.com/problems/n-ary-tree-level-order-traversal/submissions/

解析

  建立队列,将根节点入队,创造循环遍历队中每个结点将其孩子入队,但这题要注意要分树的每一行进行遍历,先遍历这一行的结点将其放到结果数组中,再以此遍历下一行的结点。

实现

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
/*
// Definition for a Node.
class Node {
public:
int val;
vector<Node*> children;

Node() {}

Node(int _val, vector<Node*> _children) {
val = _val;
children = _children;
}
};
*/
class Solution {
public:
vector<vector<int>> levelOrder(Node* root) {
//创造队列
queue<Node*> q;
vector<vector<int>> treeVec;
if(root == nullptr)
{
return treeVec;
}
//放入根结点
q.push(root);
//遍历队中结点
while(!q.empty())
{
//得到这一行的长度
int sz = q.size();
vector<int> rowNode;
//遍历这一行
while(sz--)
{
Node* curNode = q.front();
q.pop();
//将这一行结点的值放到rowNode中
rowNode.push_back(curNode->val);
//将这一行中每个结点的孩子入队
for(auto& chd : curNode->children)
{
q.push(chd);
}
}
treeVec.push_back(rowNode);
}
return treeVec;
}
};

例3 腐烂的橘子

  力扣:
https://leetcode-cn.com/problems/rotting-oranges/

解析

  类似于N叉树的遍历。每一分钟腐烂一层,遍历每一层即可,最后搜索结束后判断是否还有新鲜橘子。

实现

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
int nextP[4][2] = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}};
class Solution {
public:
int orangesRotting(vector<vector<int>>& grid) {
queue<pair<int, int>> q;
int row = grid.size();
int col = grid[0].size();
//找出第一批已经坏掉的橘子,入队
for(int i = 0; i < row; i++)
{
for(int j = 0; j < col; j++)
{
if(grid[i][j] == 2)
{
q.push(make_pair(i, j));
}
}
}
//初始状态
int minRet = 0;
while(!q.empty())
{
//跟N叉树遍历一样,这里遍历一层代表要消耗一分钟
int flag = 0;
int sz = q.size();
//遍历这一层
while(sz--)
{
pair<int, int> curPos = q.front();
q.pop();
for(int i = 0; i < 4; i++)
{
int nx = curPos.first + nextP[i][0];
int ny = curPos.second + nextP[i][1];
if(nx >= row || nx < 0 || ny >= col || ny < 0)
{
continue;
}
if(grid[nx][ny] == 1)
{
flag = 1;
grid[nx][ny] = 2;
q.push(make_pair(nx, ny));
}
}
}
if(flag)
{
++minRet;
}
}
for(int i = 0; i < row; i++)
{
for(int j = 0; j < col; j++)
{
if(grid[i][j] == 1)
{
return -1;
}
}
}
return minRet;
}
};
-------------本文结束感谢您的阅读!-------------
记录学习每一分,感谢您的赞助