搜索算法---深度优先搜索
Posted Moua
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了搜索算法---深度优先搜索相关的知识,希望对你有一定的参考价值。
目录
一、通过下面一个问题总结深度优先搜索
问题:有编号为1~3的三张牌和编号为1~3的三个盒子,将三张牌放到三个盒子中且每个盒子只能放一张牌,问:一共有多少种方法?
- 根据题意,每个盒子放的牌有三种可能(1号盒子放的牌可能是1、2或3,2号...)。假定,从1号盒子开始放,接着放2号、3号。
- 给1号盒子放牌时,由于1号盒子是第一个放的,因此有三种选择:1、2、3号牌。假定,第一次放入1号牌,第二次放入2号牌...
- 给2号盒子放牌,此时1号盒子已经放入了1号牌,因此2号盒子只能从2、3号盒子选一张牌放入,假定选2号牌放入。
- 给3号盒子放牌,此时只剩下3号牌还没有放入,因此3号盒子放入3号牌。
- 继续往后走,已经没有盒子也没有牌了,也就是说一种方法已经完成。此时,需要折返,找另外一种方法。
- 折返到3号盒子,拿出三号盒子中的牌,此时只有3号牌没被放入,但是3号牌已经在3号盒子放入过一次,不能再次放入,因此继续折返。
- 折返到2号盒子,拿出2号盒子中的牌,此时有2、3号牌没有放入,可以将3号牌放入2号盒子,继续往后走,将2号牌放入3号盒子。
- 在继续走,后边又没有盒子了,第2中方法完成。继续折返...依此类推,直到找到所有方法。
代码实现:
void DFS(int index, std::vector<int> box, std::vector<int> brand)
{
//边界处理
if (index == box.size())
{
//打印
for (int i = 1; i < box.size();i++)
std::cout << box[i] << " ";
std::cout << std::endl;
return;
}
for (int i = 1; i < box.size(); i++)
{
//处理当前盒子
if (brand[i] == 0)
{
box[index] = i;
brand[i] = 1;
//处理下一个盒子
DFS(index + 1, box, brand);
//回收盒子
brand[i] = 0;
}
}
}
1、深度优先搜索的一般步骤
- 分析问题,找出问题的一般规律。
- 按照某一特定路线进行搜索。
- 判断是否到达搜索边界,如果到达则折返进行搜索,否则继续进行深度优先搜索。
- 输出结果
Dfs(当前这一步的处理逻辑)
{
1. 判断边界,是否已经一条道走到黑了:向上回退
2. 尝试当下的每一种可能
3. 确定一种可能之后,继续下一步 Dfs(下一步)
}
2、深度优先搜索的特点
- 深度优先搜索(DFS,Depth First Serach)算法是一种方法尝试结束,在折返尝试另一种方法。
- 深度优先搜索一般采用递归实现
3、深度优先搜索的代码实现
- 处理当前状态
- 处理下一个状态
- 折返
- 边界处理
注意:深度优先搜索算法一般采用递归实现,处理边界是解决问题的关键。
二、例题分析
1、员工重要性
根据题目可以看出这是一个典型的深度优先搜索的题目。例如:[[1, 5, [2, 3]], [2, 3, [4,5]], [3, 3, [6]],[4,2,[]],[5,2,[7]],[6,3,[]],[7,1,[]]], 1
搜索顺序:1->2->4->5->->3->6
临界条件:当某一条路径走到头了,就需要折返,很明显在这里某一个节点的subordinates为NULL时就该折返了。即employees[i]->subordinates.empty()。
代码描述:
class Solution {
public:
void _getImportance(vector<Employee*>& employees,int id,int& ret)
{
//处理当前状态
int index = 0;
for(int i = 0;i < employees.size();i++)
{
if(employees[i]->id == id)
{
ret += employees[i]->importance;
index = i;
}
}
//临界
if((employees[index]->subordinates).empty())
return;
//处理下一个状态
for(int i = 0;i < (employees[index]->subordinates).size();i++)
_getImportance(employees,(employees[index]->subordinates)[i],ret);
}
int getImportance(vector<Employee*> employees, int id) {
int ret = 0;
_getImportance(employees,id,ret);
return ret;
}
};
2、图像渲染
思路:深度优先搜索
从当前像素点开始渲染,渲染完后继续渲染上下左右方向的像素点。在使用递归时,结束条件有两个:坐标非法;颜色和原始颜色不同(不需要渲染)。
注意:出递归,所以有可能存在渲染完成后和渲染之前是一样的(新颜色和就颜色相同),就有可能导致无法退每渲染完一个像素点,在原始图画上将该位置标记为-1(像素值为0~65535,只要不在这范围都可以),使用一个新的数组保存渲染后的图像。
class Solution {
public:
void _floodFill(vector<vector<int>>& img,vector<vector<int>>& ret,int src,int sc,int oldColor,int newColor)
{
/*结束条件:
*1、坐标不存在
*2、颜色和原始颜色不同
*/
if(src < 0 || src >= img.size() || sc < 0 || sc >= img[0].size())
{
//位置不存在
return;
}
if(img[src][sc] != oldColor)
{
//与初始颜色不同
return;
}
//进行渲染
img[src][sc] = -1;
ret[src][sc] = newColor;
//依次从上下左右四个方向进行渲染
_floodFill(img,ret,src-1,sc,oldColor,newColor);
_floodFill(img,ret,src+1,sc,oldColor,newColor);
_floodFill(img,ret,src,sc-1,oldColor,newColor);
_floodFill(img,ret,src,sc+1,oldColor,newColor);
}
vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int newColor) {
vector<vector<int>> ret(image);
_floodFill(image,ret,sr,sc,image[sr][sc],newColor);
return ret;
}
};
3、岛屿的周长
class Solution {
public:
void _islandPerimeter(vector<vector<int>>& grid,int sr,int sc,int& ret)
{
//终止条件:不是陆地或者位置非法
//判断周长加多少并继续递归
//判断上边是否为陆地
grid[sr][sc] = -1;
if((sr-1 >= 0 && sr-1 < grid.size()) && grid[sr-1][sc] == 1)
{
//上边是陆地,递归否则+1
_islandPerimeter(grid,sr-1,sc,ret);
}
else if((sr-1 < 0 || sr-1 >= grid.size()) ||grid[sr-1][sc] != -1)
{
ret += 1;
}
//下
if(sr+1 >= 0 && sr+1 < grid.size() && grid[sr+1][sc] == 1)
{
_islandPerimeter(grid,sr+1,sc,ret);
}
else if((sr+1 < 0 || sr+1 >= grid.size()) || grid[sr+1][sc] != -1)
{
ret += 1;
}
//左
if(sc-1 >= 0 && sc-1 < grid[0].size() && grid[sr][sc-1] == 1)
{
_islandPerimeter(grid,sr,sc-1,ret);
}
else if((sc-1 < 0 || sc-1 >= grid[0].size()) || grid[sr][sc-1] != -1)
{
ret += 1;
}
//右
if(sc+1 >= 0 && sc+1 < grid[0].size() && grid[sr][sc+1] == 1)
{
_islandPerimeter(grid,sr,sc+1,ret);
}
else if((sc+1 < 0 || sc+1 >= grid[0].size()) || grid[sr][sc+1] != -1)
{
ret += 1;
}
}
int islandPerimeter(vector<vector<int>>& grid) {
//找到一块陆地
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] == 1)
{
//陆地
row = i;
col = j;
break;
}
}
}
int ret = 0;
//以找到的第一块陆地为中心,向四周发散查找
_islandPerimeter(grid,row,col,ret);
return ret;
}
};
4、被围绕的区域
思路:如果直接找被围绕的岛屿,则终止条件应该是:所给坐标为边界或者与边界相连,边界问题很难处理。因此,将问题转化成先将边界和边界相连的岛屿变成'Y',在将其他的所有O变成X,最后再将Y变成O。也就是说,使用深度优先搜索处理边界为X及与边界上X相连的X。
class Solution {
public:
void _solve(vector<vector<char>>& board,int sr,int sc)
{
//终止条件:不是O或者位置非法
if(sr < 0 || sr >= board.size() || sc < 0 || sc >= board[0].size())
return;
if(board[sr][sc] != 'O')
return;
//变成Y是O进行扩散
board[sr][sc] = 'Y';
_solve(board,sr-1,sc);
_solve(board,sr+1,sc);
_solve(board,sr,sc-1);
_solve(board,sr,sc+1);
}
void solve(vector<vector<char>>& board) {
//把边界或与边界相连的o变成Y
//找出一个边界上的o
//第一行
for(int i = 0;i < board[0].size();i++)
{
if(board[0][i] == 'O')
{
_solve(board,0,i);
}
}
//第一列
for(int i = 0;i < board.size();i++)
{
if(board[i][0] == 'O')
{
_solve(board,i,0);
}
}
//最后一行
for(int i = 0;i < board[0].size();i++)
{
if(board[board.size()-1][i] == 'O')
{
_solve(board,board.size()-1,i);
}
}
//最后一列
for(int i = 0;i < board.size();i++)
{
if(board[i][board[0].size()-1] == 'O')
{
_solve(board,i,board[0].size()-1);
}
}
//将所有的O变成x
for(int i = 1;i < board.size()-1;i++)
{
for(int j = 1;j < board[0].size()-1;j++)
{
if(board[i][j] == 'O')
board[i][j] = 'X';
}
}
//将所有的Y变成O
for(int i = 0;i < board.size();i++)
{
for(int j = 0;j < board[0].size();j++)
{
if(board[i][j] == 'Y')
board[i][j] = 'O';
}
}
}
};
5、岛屿数量
思路:对每一块岛屿进行渲染,渲染的次数就是岛屿的数量。进行渲染时,采用深度优先搜索,统计次数:采用双循环对数组济宁遍历,如果是1就开始进行渲染并且渲染次数+1
class Solution {
public:
void _numIslands(vector<vector<char>>& grid,int sr,int sc)
{
//将某一个岛屿由1变成0
//终止条件:位置不合法或者不是陆地
if(sr < 0 || sr >= grid.size() || sc < 0 || sc >= grid[0].size())
return;
if(grid[sr][sc] != '1')
return;
//将其变成0
grid[sr][sc] = '0';
//其他四个方向搜索
_numIslands(grid,sr-1,sc);
_numIslands(grid,sr+1,sc);
_numIslands(grid,sr,sc-1);
_numIslands(grid,sr,sc+1);
}
int numIslands(vector<vector<char>>& grid) {
//以每一个是陆地的点进行深度优先搜索,看一共进行了多少次深度优先搜索。
int ret = 0;
for(int i = 0;i < grid.size();i++)
{
for(int j = 0;j < grid[0].size();j++)
{
if(grid[i][j] == '1')
{
ret++;
_numIslands(grid,i,j);
}
}
}
return ret;
}
};
6、岛屿最大面积
思路:首先对每块陆地进行渲染,在渲染时统计面积。对于该数组中所有的陆地,都济宁渲染,每次渲染时找出最大的面积的岛屿。
class Solution {
public:
void _maxAreaOfIsland(vector<vector<int>>& grid,int sr,int sc,int& area)
{
//将某一个岛屿由1变成0
//终止条件:位置不合法或者不是陆地
if(sr < 0 || sr >= grid.size() || sc < 0 || sc >= grid[0].size())
return;
if(grid[sr][sc] != 1)
return;
//进行渲染并计算面积
grid[sr][sc] = 0;
area++;
//其他四个方向搜索
_maxAreaOfIsland(grid,sr-1,sc,area);
_maxAreaOfIsland(grid,sr+1,sc,area);
_maxAreaOfIsland(grid,sr,sc-1,area);
_maxAreaOfIsland(grid,sr,sc+1,area);
}
int maxAreaOfIsland(vector<vector<int>>& grid) {
//以每一个是陆地的点进行深度优先搜索,每次搜索过程中统计面积并对该岛屿进行渲染,最后找出最大面积的岛屿
int ret = 0;
for(int i = 0;i < grid.size();i++)
{
for(int j = 0;j < grid[0].size();j++)
{
if(grid[i][j] == 1)
{
int area = 0;
_maxAreaOfIsland(grid,i,j,area);
if(area > ret)
ret = area;
}
}
}
return ret;
}
};
以上是关于搜索算法---深度优先搜索的主要内容,如果未能解决你的问题,请参考以下文章
数据结构与算法图遍历算法 ( 深度优先搜索 DFS | 深度优先搜索和广度优先搜索 | 深度优先搜索基本思想 | 深度优先搜索算法步骤 | 深度优先搜索理论示例 )