搜索算法---深度优先搜索

Posted Moua

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了搜索算法---深度优先搜索相关的知识,希望对你有一定的参考价值。

目录

一、通过下面一个问题总结深度优先搜索

1、深度优先搜索的一般步骤

2、深度优先搜索的特点

3、深度优先搜索的代码实现

二、例题分析

1、员工重要性

2、图像渲染

3、岛屿的周长

4、被围绕的区域

5、岛屿数量

6、岛屿最大面积


一、通过下面一个问题总结深度优先搜索

问题:有编号为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、深度优先搜索的一般步骤

  1. 分析问题,找出问题的一般规律。
  2. 按照某一特定路线进行搜索。
  3. 判断是否到达搜索边界,如果到达则折返进行搜索,否则继续进行深度优先搜索。
  4. 输出结果
Dfs(当前这一步的处理逻辑)
{
    1. 判断边界,是否已经一条道走到黑了:向上回退
    2. 尝试当下的每一种可能
    3. 确定一种可能之后,继续下一步 Dfs(下一步)
}

2、深度优先搜索的特点

  • 深度优先搜索(DFS,Depth First Serach)算法是一种方法尝试结束,在折返尝试另一种方法。
  • 深度优先搜索一般采用递归实现

3、深度优先搜索的代码实现

  1. 处理当前状态
  2. 处理下一个状态
  3. 折返
  4. 边界处理

注意:深度优先搜索算法一般采用递归实现,处理边界是解决问题的关键。

二、例题分析

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 | 深度优先搜索和广度优先搜索 | 深度优先搜索基本思想 | 深度优先搜索算法步骤 | 深度优先搜索理论示例 )

七十九深度和广度优先搜索算法

图相关算法

基本算法——深度优先搜索(DFS)和广度优先搜索(BFS)

算法图的广度优先搜索和深度优先搜索