图的深度优先搜索1
深度优先搜索的核心就是每次都往下走没有走过的节点,如果走到末端就回溯并继续往下走没有走过的节点
岛屿的周长 解法二(非递归DFS)未过OJ

员工的重要性
给定一个保存员工信息的数据结构,它包含了员工 唯一的 id ,重要度 和 直系下属的 id 。
比如,员工 1 是员工 2 的领导,员工 2 是员工 3 的领导。他们相应的重要度为 15 , 10 , 5 。那么员工 1 的数据结构是 [1, 15, [2]] ,员工 2的 数据结构是 [2, 10, [3]] ,员工 3 的数据结构是 [3, 5, []] 。注意虽然员工 3 也是员工 1 的一个下属,但是由于 并不是直系 下属,因此没有体现在员工 1 的数据结构中。
现在输入一个公司的所有员工信息,以及单个员工 id ,返回这个员工和他所有下属的重要度之和。
示例:
输入:[[1, 5, [2, 3]], [2, 3, []], [3, 3, []]], 1
输出:11
解释:
员工 1 自身的重要度是 5 ,他有两个直系下属 2 和 3 ,而且 2 和 3 的重要度均为 3 。因此员工 1 的总重要度是 5 + 3 + 3 = 11 。
这个题就是有向图的深度搜索
from graphviz import Digraph
dot = Digraph(comment='The Round Table')
dot.node('A', '1, 5')
dot.node('B', '2, 3')
dot.node('C', '3, 3')
dot.edges(['AB', 'AC'])
dot
class Solution {
public:
int getImportance(vector<Employee*> employees, int id) {
//建立id-->importance的map
map<int, int> id_im;
//建立id-->下属的map
map<int, vector<int>> id_sub;
for(int i=0; i<employees.size(); ++i){
id_im[employees[i]->id] = employees[i]->importance;
id_sub[employees[i]->id] = employees[i]->subordinates;
}
int res = 0;
dfs(id_sub, id_im, id, res);
return res;
}
void dfs(map<int, vector<int>> id_sub, map<int, int> id_im, int id, int &res){
res += id_im[id];
vector<int> &sub = id_sub[id];
if(!sub.size()) return;
for(int i=0; i<sub.size(); ++i)
dfs(id_sub, id_im, sub[i], res);
}
};
建立id–>importance的map和建立id–>下属的map是为了更快的通过员工id找到重要度和下属id
map换成unordered_map效率有所提高,map底层是AVL树,[key]复杂度O(logN),unordered_map底层是hash所以[key]复杂度O(1),速度更快
将上述递归方式深度搜索改成非递归方式
class Solution {
public:
int getImportance(vector<Employee*> employees, int id) {
//建立id-->importance的map
unordered_map<int, int> id_im;
//建立id-->下属的map
unordered_map<int, vector<int>> id_sub;
for(int i=0; i<employees.size(); ++i){
id_im[employees[i]->id] = employees[i]->importance;
id_sub[employees[i]->id] = employees[i]->subordinates;
}
int res = 0;
dfs_un_recursion(id_sub, id_im, id, res);
return res;
}
void dfs_recursion(unordered_map<int, vector<int>> id_sub, unordered_map<int, int> id_im, int id, int &res){
res += id_im[id];
vector<int> &sub = id_sub[id];
if(!sub.size()) return;
for(int i=0; i<sub.size(); ++i)
dfs_recursion(id_sub, id_im, sub[i], res);
}
//非递归dfs
void dfs_un_recursion(unordered_map<int, vector<int>> id_sub, unordered_map<int, int> id_im, int id, int &res){
//记录节点是否访问过
unordered_map<int, bool> visited;
for(unordered_map<int, int>::iterator it = id_im.begin(); it!=id_im.end(); ++it)
visited[it->first] = false;
stack<int> st_route;
st_route.push(id);
while(!st_route.empty()){
int cur = st_route.top();
vector<int> &sub = id_sub[cur];
char flag = 1; //需要回溯
for(int i=0; i<sub.size(); ++i){
if(!visited[sub[i]]){ //节点没有被访问过
visited[sub[i]] = true;
st_route.push(sub[i]);
flag = 0;
break;
}
}
if(!sub.size() || flag){ //回溯
res += id_im[st_route.top()];
//此时栈中就是一条深度搜索路径
st_route.pop();
if(!st_route.empty())
cur = st_route.top();
}
}
}
};
内存消耗和执行用时右明显提升
继续优化
不建立id–>importance的map、不建立id–>下属的map、并使用非递归方式
class Solution {
public:
int getImportance(vector<Employee*> employees, int id) {
int res = 0;
dfs_un_recursion(employees, id, res);
return res;
}
//非递归dfs
void dfs_un_recursion(vector<Employee*> employees, int id, int &res){
//记录节点是否访问过
unordered_map<int, bool> visited;
for(int i=0; i<employees.size(); ++i)
visited[employees[i]->id] = false;
stack<int> st_route;
st_route.push(id);
while(!st_route.empty()){
int cur = st_route.top();
vector<int> &sub = id_sub(employees, cur);
char flag = 1; //需要回溯
for(int i=0; i<sub.size(); ++i){
if(!visited[sub[i]]){ //节点没有被访问过
visited[sub[i]] = true;
st_route.push(sub[i]);
flag = 0;
break;
}
}
if(!sub.size() || flag){ //回溯
res += id_im(employees, st_route.top());
//此时栈中就是一条深度搜索路径
st_route.pop();
if(!st_route.empty())
cur = st_route.top();
}
}
}
vector<int>& id_sub(vector<Employee*> employees, int id){
for(int i=0; i<employees.size(); ++i)
if(id == employees[i]->id)
return employees[i]->subordinates;
return employees[0]->subordinates;
}
int id_im(vector<Employee*> employees, int id){
for(int i=0; i<employees.size(); ++i)
if(id == employees[i]->id)
return employees[i]->importance;
return -1;
}
};

图像渲染
有一幅以二维整数数组表示的图画,每一个整数表示该图画的像素值大小,数值在 0 到 65535 之间。
给你一个坐标(sr, sc)表示图像渲染开始的像素值(行 ,列)和一个新的颜色值newColor,让你重新上色这幅图像。
为了完成上色工作,从初始坐标开始,记录初始坐标的上下左右四个方向上像素值与初始坐标相同的相连像素点,接着再记录这四个方向上符合条件的像素点与他们对应四个方向上像素值与初始坐标相同的相连像素点,……,重复该过程。将所有有记录的像素点的颜色值改为新的颜色值。
最后返回经过上色渲染后的图像。
示例:
输入:
image = [[1,1,1],[1,1,0],[1,0,1]]
sr = 1, sc = 1, newColor = 2
输出: [[2,2,2],[2,2,0],[2,0,1]]
解析:
在图像的正中间,(坐标(sr,sc)=(1,1)),
在路径上所有符合条件的像素点的颜色都被更改成2。
注意,右下角的像素没有更改为2,
因为它不是在上下左右四个方向上与初始点相连的像素点。
深度优先搜索(递归)
class Solution {
public:
vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int newColor) {
//建立像素点是否访问过的数组
vector<vector<int> > visited(image.size(), vector<int>(image[0].size(), 0) );
int oldColor = image[sr][sc];
dfs_recursion(visited, image, sr, sc, oldColor, newColor);
return image;
}
int spread[4][2] = {{0, -1}, {-1, 0}, {0, 1}, {1, 0}}; //左,上,右,下
//递归方式深度优先搜索
void dfs_recursion(vector<vector<int> > &visited, vector<vector<int>>& image, int sr, int sc, int oldColor, int newColor){
image[sr][sc] = newColor;
visited[sr][sc] = 1;
for(int i=0; i<4; ++i){ //向四周扩散
int spread_sr = sr+spread[i][0];
int spread_sc = sc+spread[i][1];
if(spread_sr>=0 && spread_sr<image.size() &&
spread_sc>=0 && spread_sc<image[0].size() &&
oldColor == image[spread_sr][spread_sc] &&
0 == visited[spread_sr][spread_sc]){
dfs_recursion(visited, image, spread_sr, spread_sc, oldColor, newColor);
}
}
}
};
深度优先搜索(非递归)
class Solution {
public:
vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int newColor) {
//建立像素点是否访问过的数组
vector<vector<int> > visited(image.size(), vector<int>(image[0].size(), 0) );
int oldColor = image[sr][sc];
dfs_non_recursion(visited, image, sr, sc, oldColor, newColor);
return image;
}
int spread[4][2] = {{0, -1}, {-1, 0}, {0, 1}, {1, 0}}; //左,上,右,下
//非递归方式深度优先搜索
void dfs_non_recursion(vector<vector<int> > &visited, vector<vector<int>>& image, int sr, int sc, int oldColor, int newColor){
stack<int> st_route_sr;
stack<int> st_route_sc;
st_route_sr.push(sr);
st_route_sc.push(sc);
while(!st_route_sc.empty()){
int cur_sr = st_route_sr.top();
int cur_sc = st_route_sc.top();
char flag = 1; //是否需要回溯
for(int i=0; i<4; ++i){ //向四周扩散
int spread_sr = cur_sr+spread[i][0];
int spread_sc = cur_sc+spread[i][1];
if(spread_sr>=0 && spread_sr<image.size() && spread_sc>=0 && spread_sc<image[0].size() &&
oldColor == image[spread_sr][spread_sc] && 0 == visited[spread_sr][spread_sc]){
visited[spread_sr][spread_sc] = 1;
st_route_sr.push(spread_sr);
st_route_sc.push(spread_sc);
flag = 0;
break;
}
}
if(flag){ //回溯
image[cur_sr][cur_sc] = newColor;
visited[cur_sr][cur_sc] = 1;
st_route_sr.pop();
st_route_sc.pop();
if(!st_route_sr.empty()){
cur_sr = st_route_sr.top();
cur_sc = st_route_sc.top();
}
}
}
}
};

岛屿的周长
给定一个 row x col 的二维网格地图 grid ,其中:grid[i][j] = 1 表示陆地, grid[i][j] = 0 表示水域。
网格中的格子 水平和垂直 方向相连(对角线方向不相连)。整个网格被水完全包围,但其中恰好有一个岛屿(或者说,一个或多个表示陆地的格子相连组成的岛屿)。
岛屿中没有“湖”(“湖” 指水域在岛屿内部且不和岛屿周围的水相连)。格子是边长为 1 的正方形。网格为长方形,且宽度和高度均不超过 100 。计算这个岛屿的周长。
示例:
输入:grid = [[0,1,0,0],[1,1,1,0],[0,1,0,0],[1,1,0,0]]
输出:16
解释:它的周长是上面图片中的 16 个黄色的边
解法一(递归方式深度优先搜索)
岛屿的周长就是岛屿方格和非岛屿方格相邻的边以及边界的数量
岛屿方格和非岛屿方格从一个岛屿方格走向一个非岛屿方格,就将周长加 1
class Solution {
public:
int islandPerimeter(vector<vector<int>>& grid) {
for(int r=0; r<grid.size(); ++r){
for(int c=0; c<grid[0].size(); ++c){
if(grid[r][c] == 1)
return dfs(grid, r, c);
}
}
return 0;
}
int dfs(vector<vector<int>>& grid, int r, int c){
if(!(0 <= r && r<grid.size() && 0 <= c && c<grid[0].size())) //边界在这里返回1了
return 1;
if(grid[r][c] == 0) //从岛屿移入非岛屿计数+1
return 1;
if(grid[r][c] != 1) //已遍历过的陆地方格不再遍历
return 0;
grid[r][c] = 2; //2 表示已遍历过的陆地方格
return dfs(grid, r-1, c)+
dfs(grid, r+1, c)+
dfs(grid, r, c-1)+
dfs(grid, r, c+1);
}
};
解法二(非递归DFS)
找规律,陆地三个面是边界或者是水域则+1
class Solution {
public:
int islandPerimeter(vector<vector<int>>& grid) {
if(grid.size() == 1 && grid[0].size() == 1 && grid.size() == 1) return 4;
//建立像素点是否访问过的数组
vector<vector<char> > visited(grid.size(), vector<char>(grid[0].size(), 0) );
int res = 0;
int start_x = 0, start_y = 0;
for(int i=0; i<grid.size(); ++i){
for(int j=0; j<grid[0].size(); ++j){
if(grid[i][j] == 1){
start_x = i;
start_y = j;
break;
}
}
}
dfs_non_recursion(grid, visited, start_x, start_y, res);
return res;
}
int spread[4][2] = {{0, -1}, {-1, 0}, {0, 1}, {1, 0}}; //左,上,右,下
void dfs_non_recursion(vector<vector<int>>& grid, vector<vector<char>>& visited, int x, int y, int &res) {
if (visited[x][y] == 1)return;
visited[x][y] = 1;
if(isIsolated(x, y, grid)) ++res;
stack<int> st_route_x;
stack<int> st_route_y;
st_route_x.push(x);
st_route_y.push(y);
while (!st_route_x.empty()) {
int cur_x = st_route_x.top();
int cur_y = st_route_y.top();
int flag = 1; //是否需要回溯
for (int i = 0; i < 4; ++i) {
int spread_x = cur_x + spread[i][0];
int spread_y = cur_y + spread[i][1];
if (spread_x >= 0 && spread_x < grid.size() && spread_y >= 0 && spread_y < grid[0].size() &&
grid[spread_x][spread_y] == 1 && 0 == visited[spread_x][spread_y]) {
visited[spread_x][spread_y] = 1;
res+=2;
//陆地三个面是边界或者是水域则+1
if(isIsolated(spread_x, spread_y, grid))
++res;
st_route_x.push(spread_x);
st_route_y.push(spread_y);
flag = 0;
break;
}
}
if (flag) {
st_route_x.pop();
st_route_y.pop();
if (!st_route_x.empty()) {
cur_x = st_route_x.top();
cur_y = st_route_y.top();
}
}
}
}
bool isIsolated(int x, int y, vector<vector<int>>& grid){
int count = 0;
count += !x||x==grid.size()-1? 1:0;
count += !y||y==grid[0].size()-1? 1:0;
for (int i = 0; i < 4; ++i) {
int spread_x = x + spread[i][0];
int spread_y = y + spread[i][1];
if (spread_x >= 0 && spread_x < grid.size() && spread_y >= 0 && spread_y < grid[0].size()){
if(grid[spread_x][spread_y] == 0)
++count;
}
}
if(count == 3) return true;
return false;
}
};
1084 / 5833 个通过测试用例
。。。
解法三(找规律)
一块土地原则上会带来 4 个周长,但岛上的土地存在接壤,每一条接壤,会减掉 2 个边长。
所以,总周长 = 4 * 土地个数 - 2 * 接壤边的条数。
遍历矩阵,遍历到土地,就 land++,如果它的右/下边也是土地,则 border++,遍历结束后代入公式
class Solution {
public:
int islandPerimeter(vector<vector<int>>& grid) {
int land = 0; //土地个数
int border = 0; //接壤边界数
//遍历矩阵,遍历到土地,就 land++,如果它的右/下边也是土地,则 border++
for (int i = 0; i < grid.size(); ++i) {
for (int j = 0; j < grid[0].size(); ++j) {
if (grid[i][j] == 1){ land++;
if (j < grid[0].size() - 1 && grid[i][j + 1] == 1)
border++;
if (i < grid.size() - 1 && grid[i + 1][j] == 1)
border++;
}
}
}
return 4 * land - 2 * border;
}
};

被围绕的区域
反向思维
如果直接找封闭区域,不太容易,因为开放区域的方格也可能位于矩阵中间位置
观察发现开放区域必有方格位于四边边界,因此可以先将开放区域标记出来,然后深度优先搜索将封闭区域’O’–>‘X’
class Solution {
public:
void solve(vector<vector<char>>& board) {
//开放区域必有点位于四边边界
//建立搜索矩阵-->表示访问过的点
vector<vector<char>> visited(board.size(), vector<char>(board[0].size(), 0));
//将开放区域标记成访问过
int row = board.size();
int col = board[0].size();
//上下边界
for(int i=0; i<col; ++i){
if('O' == board[0][i])
dfs_non_recursion(board, visited, 0, i, false);
//下边界
if('O' == board[row-1][i])
dfs_non_recursion(board, visited, row-1, i, false);
}
//左右边界
for(int i=1; i<row-1; ++i){
if('O' == board[i][0])
dfs_non_recursion(board, visited, i, 0, false);
if('O' == board[i][col-1])
dfs_non_recursion(board, visited, i, col-1, false);
}
//更改封闭区域'O'-->'X'
for(int i=1; i<row-1; ++i){
for(int j=1; j<col-1; ++j){
if('O' == board[i][j])
dfs_non_recursion(board, visited, i, j, true);
}
}
}
int spread[4][2] = {{0, -1}, {-1, 0}, {0, 1}, {1, 0}}; //左,上,右,下
void dfs_non_recursion(vector<vector<char>>& board, vector<vector<char>>& visited, int x, int y, bool change) {
if (visited[x][y] == 1)return;
visited[x][y] = 1;
stack<int> st_route_x;
stack<int> st_route_y;
st_route_x.push(x);
st_route_y.push(y);
while (!st_route_x.empty()) {
int cur_x = st_route_x.top();
int cur_y = st_route_y.top();
int flag = 1; //是否需要回溯
for (int i = 0; i < 4; ++i) {
int spread_x = cur_x + spread[i][0];
int spread_y = cur_y + spread[i][1];
if (spread_x >= 0 && spread_x < board.size() && spread_y >= 0 && spread_y < board[0].size() &&
board[spread_x][spread_y] == 'O' && 0 == visited[spread_x][spread_y]) {
visited[spread_x][spread_y] = 1;
st_route_x.push(spread_x);
st_route_y.push(spread_y);
flag = 0;
break;
}
}
if (flag) {
if (change) board[cur_x][cur_y] = 'X';
st_route_x.pop();
st_route_y.pop();
if (!st_route_x.empty()) {
cur_x = st_route_x.top();
cur_y = st_route_y.top();
}
}
}
}
};

岛屿数量
和图像渲染的思路一样
class Solution {
public:
int numIslands(vector<vector<char>>& grid) {
//建立搜索矩阵-->表示访问过的点
vector<vector<char>> visited(grid.size(), vector<char>(grid[0].size(), 0));
//计数连片区域
int res = 0;
for(int i=0; i<grid.size(); ++i){
for(int j=0; j<grid[0].size(); ++j){
if('1' == grid[i][j] && 0 == visited[i][j]){
dfs_non_recursion(grid, visited, i, j);
++res;
}
}
}
return res;
}
int spread[4][2] = {{0, -1}, {-1, 0}, {0, 1}, {1, 0}}; //左,上,右,下
//深度优先搜索标记访问过的连片区域
void dfs_non_recursion(vector<vector<char>>& board, vector<vector<char>>& visited, int x, int y) {
if (visited[x][y] == 1)return;
visited[x][y] = 1;
stack<int> st_route_x;
stack<int> st_route_y;
st_route_x.push(x);
st_route_y.push(y);
while (!st_route_x.empty()) {
int cur_x = st_route_x.top();
int cur_y = st_route_y.top();
int flag = 1; //是否需要回溯
for (int i = 0; i < 4; ++i) {
int spread_x = cur_x + spread[i][0];
int spread_y = cur_y + spread[i][1];
if (spread_x >= 0 && spread_x < board.size() && spread_y >= 0 && spread_y < board[0].size() &&
board[spread_x][spread_y] == '1' && 0 == visited[spread_x][spread_y]) {
visited[spread_x][spread_y] = 1;
st_route_x.push(spread_x);
st_route_y.push(spread_y);
flag = 0;
break;
}
}
if (flag) { //回溯
st_route_x.pop();
st_route_y.pop();
if (!st_route_x.empty()) {
cur_x = st_route_x.top();
cur_y = st_route_y.top();
}
}
}
}
};

岛屿的最大面积
和图像渲染的思路一样
只需要计数每个连片区域大小,最后取最大值
class Solution {
public:
int maxAreaOfIsland(vector<vector<int>>& grid) {
//建立搜索矩阵-->表示访问过的点
vector<vector<char>> visited(grid.size(), vector<char>(grid[0].size(), 0));
//计数连片区域,取最大值
int max = 0, res = 0;
for(int i=0; i<grid.size(); ++i){
for(int j=0; j<grid[0].size(); ++j){
if(1 == grid[i][j] && 0 == visited[i][j]){
res = 0;
dfs_non_recursion(grid, visited, i, j, res);
max = res>max? res:max;
}
}
}
return max;
}
int spread[4][2] = {{0, -1}, {-1, 0}, {0, 1}, {1, 0}}; //左,上,右,下
//深度优先搜索标记访问过的连片区域
void dfs_non_recursion(vector<vector<int>>& grid, vector<vector<char>>& visited, int x, int y, int &res) {
if (visited[x][y] == 1)return;
visited[x][y] = 1;
++res;
stack<int> st_route_x;
stack<int> st_route_y;
st_route_x.push(x);
st_route_y.push(y);
while (!st_route_x.empty()) {
int cur_x = st_route_x.top();
int cur_y = st_route_y.top();
int flag = 1; //是否需要回溯
for (int i = 0; i < 4; ++i) {
int spread_x = cur_x + spread[i][0];
int spread_y = cur_y + spread[i][1];
if (spread_x >= 0 && spread_x < grid.size() && spread_y >= 0 && spread_y < grid[0].size() &&
grid[spread_x][spread_y] == 1 && 0 == visited[spread_x][spread_y]) {
visited[spread_x][spread_y] = 1;
++res;
st_route_x.push(spread_x);
st_route_y.push(spread_y);
flag = 0;
break;
}
}
if (flag) { //回溯
st_route_x.pop();
st_route_y.pop();
if (!st_route_x.empty()) {
cur_x = st_route_x.top();
cur_y = st_route_y.top();
}
}
}
}
};

Depth First Search ↩︎