图论的题目目前分为5种:

  • 1 图遍历 – dfs或者bfs为主
  • 2 并查集 – 查询某两个节点是否存在同一集合
  • 3 拓扑排序
  • 4 最小生成树 – Prim, Kruskal
  • 5 最短路径
    • 单源最短路径 – Dijkstra,Bell_ford
    • 多源最短路径 – Folyd

1 图遍历模版

今天我们关注图的遍历,从遍历模板中我们已经得到了图该如何进行遍历,此处我们再复习一下:

1.1 DFS

int dir[4][2] = {0, 1, 0, -1, 1, 0, -1, 0};

void DFS(vector& grid, vector& visited, int x, int y){
	if (visited[x][y] || grid[x][y] == '0') return ;
	visited[x][y] = true;
	for(int i=0; i<4; i++){
		int next_x = x + dir[i][0];
		int next_y = y + dir[i][1];
		if (next_x < 0 || next_x > grid.size()) || next_y < 0 || next_y > grid[0].size()) continue;
		DFS(grid, visited, next_x, next_y);
	}
}

1.2 BFS

int dir[4][2] = {0, 1, 0, -1, 1, 0, -1, 0};

void BFS(vector>& grid, vector>& visited, int x, int y){
	queue> que;
	que.push({x, y});
	visited[x][y] = true;
	while(!que.empty()){
		for(int i=0; i<4; i++){
			pair node = que.front(); que.pop();
			int next_x = x + node.first;
			int next_y = y + node.second;
			if (next_x < 0 || next_x > grid.size() || next_y < 0 || next_y > grid[0].size()) continue;
			if (grid[next_x][next_y] != '0' && !visited[next_x][next_y]){
				que.push({next_x, next_y});
				visited[next_x][next_y];
			}
		}
	}
}

2 真题演练

2.1 岛屿数量

解题思路:

  • 1 利用图遍历将同一块岛屿内的陆地全部访问
  • 2 图遍历需要注意的问题:
    • 入口中返回条件:已经被访问过了或者不在岛屿中
    • 上下探索条件:不能访问岛屿的边界之外
2.2.1 DFS版本
#include <iostream>
#include <vector>
using namespace std;

int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 四个方向
void dfs(vector<vector<int>>& grid, vector<vector<bool>>& visited, int x, int y){
    if (visited[x][y] == true || grid[x][y] == 0) return ;
    visited[x][y] = true;
    for(int i=0; i < 4; i++){
        int next_x = x + dir[i][0];
        int next_y = y + dir[i][1];
        if (next_x < 0 || next_y < 0 || next_x >= grid.size() || next_y >= grid[0].size()) continue;
        dfs(grid, visited, next_x, next_y);
    }
}

int main(){
    // 1 处理输入
    int n, m;
    cin >> n >> m;
    vector<vector<int>> grid(n, vector<int>(m, 0));
    int x;
    for(int i=0; i<n; i++){
        for(int j=0; j<m; j++){
            cin >> x;
            if (x) grid[i][j] = 1;
        }
    }
    // 2 处理岛屿数量
    int result = 0;
    vector<vector<bool>> visited(n, vector<bool>(m, false));
    for(int i=0; i<n; i++){
        for(int j=0; j<m; j++){
            if (visited[i][j] == false && grid[i][j] == 1){
                result++;
                dfs(grid, visited, i, j);
            }
        }
    }
    // 3 打印结果
    cout << result;
    return 0;
}
2.1.2 BFS版本
#include <iostream>
#include <queue>
#include <vector>
using namespace std;

int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 四个方向
void bfs(vector<vector<int>>& grid, vector<vector<bool>>& visited, int x, int y){
    queue<pair<int, int>> myque;
    myque.push({x, y});
    visited[x][y]=true;
    while(!myque.empty()){
        pair<int, int> node = myque.front(); myque.pop();
        for(int i=0; i<4; i++){
            int next_x = node.first + dir[i][0];
            int next_y = node.second + dir[i][1];
            if (next_x < 0 || next_x >= grid.size() || next_y < 0 || next_y >= grid[0].size()) continue;
            if (visited[next_x][next_y]==false && grid[next_x][next_y]==1){
                myque.push({next_x, next_y});
                visited[next_x][next_y] = true;
            }
            
        }
    }
}

int main(){
    // 1 处理输入
    int n, m;
    cin >> n >> m;
    vector<vector<int>> grid(n, vector<int>(m, 0));
    int x;
    for(int i=0; i<n; i++){
        for(int j=0; j<m; j++){
            cin >> x;
            if (x) grid[i][j] = 1;
        }
    }
    // 2 处理岛屿数量
    int result = 0;
    vector<vector<bool>> visited(n, vector<bool>(m, false));
    for(int i=0; i<n; i++){
        for(int j=0; j<m; j++){
            if (visited[i][j] == false && grid[i][j] == 1){
                result++;
                bfs(grid, visited, i, j);
            }
        }
    }
    // 3 打印结果
    cout << result;
    return 0;
}

2.2 岛屿的最大面积

题目描述
给定一个由 1(陆地)和 0(水)组成的矩阵,计算岛屿的最大面积。岛屿面积的计算方式为组成岛屿的陆地的总数。岛屿由水平方向或垂直方向上相邻的陆地连接而成,并且四周都是水域。你可以假设矩阵外均被水包围。
输入描述
第一行包含两个整数 N, M,表示矩阵的行数和列数。后续 N 行,每行包含 M 个数字,数字为 1 或者 0,表示岛屿的单元格。
输出描述
输出一个整数,表示岛屿的最大面积。如果不存在岛屿,则输出 0。
输入示例
4 5
1 1 0 0 0
1 1 0 0 0
0 0 1 0 0
0 0 0 1 1
输出示例
4

解题思路:

  • 1 使用图遍历算法,在每个岛屿内部遍历时记录走过的陆地的数量,保留最大值给全局变量。
  • 2 以下算法为了方便只使用DFS。
2.2.1 DFS

我们可以看到思路很简单,使用了一个count全局变量用来记录递归中遍历过的陆地。

#include <iostream>
#include <vector>
#include <climits>
using namespace std;

int count = 0;
int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 四个方向
void dfs(vector<vector<int>>& grid, vector<vector<bool>>& visited, int x, int y){
    if (visited[x][y] == true || grid[x][y] == 0) return ;
    visited[x][y] = true;
    count++;
    for(int i=0; i < 4; i++){
        int next_x = x + dir[i][0];
        int next_y = y + dir[i][1];
        if (next_x < 0 || next_y < 0 || next_x >= grid.size() || next_y >= grid[0].size()) continue;
        dfs(grid, visited, next_x, next_y);
    }
}

int main(){
    // 1 处理输入
    int n, m;
    cin >> n >> m;
    vector<vector<int>> grid(n, vector<int>(m, 0));
    int x;
    for(int i=0; i<n; i++){
        for(int j=0; j<m; j++){
            cin >> x;
            if (x) grid[i][j] = 1;
        }
    }
    // 2 处理岛屿数量
    int result = 0;
    vector<vector<bool>> visited(n, vector<bool>(m, false));
    for(int i=0; i<n; i++){
        for(int j=0; j<m; j++){
            if (visited[i][j] == false && grid[i][j] == 1){
                count = 0;
                dfs(grid, visited, i, j);
                result = max(result, count);
            }
        }
    }
    // 3 打印结果
    cout << result;
    return 0;
}

2.3 孤岛的总面积

题目描述

给定一个由 1(陆地)和 0(水)组成的矩阵,岛屿指的是由水平或垂直方向上相邻的陆地单元格组成的区域,且完全被水域单元格包围。孤岛是那些位于矩阵内部、所有单元格都不接触边缘的岛屿。

现在你需要计算所有孤岛的总面积,岛屿面积的计算方式为组成岛屿的陆地的总数。

输入描述
第一行包含两个整数 N, M,表示矩阵的行数和列数。之后 N 行,每行包含 M 个数字,数字为 1 或者 0。
输出描述
输出一个整数,表示所有孤岛的总面积,如果不存在孤岛,则输出 0。
输入示例
4 5
1 1 0 0 0
1 1 0 0 0
0 0 1 0 0
0 0 0 1 1
输出示例
1

解题思路:

  • 1 想清楚什么是孤岛,如何判断孤岛,再去进行图遍历
  • 2 孤岛的定义:没有触碰到边缘。

需要注意的地方:

  • 1 不能运行到一半发现边缘就立即返回,这样会导致下一次还未来得及将visited[i][j]置为True的地方被当做是孤岛面积。
  • 2 故需要全部遍历完成后,如果遇到边缘,则整个遍历面积为0.
2.3.1 DFS
#include <iostream>
#include <vector>
#include <climits>
using namespace std;

int count = 0;
int flag = 0;
int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 四个方向
void dfs(vector<vector<int>>& grid, vector<vector<bool>>& visited, int x, int y){
    if (visited[x][y] == true || grid[x][y] == 0) return ;
    visited[x][y] = true;
    count++;
    for(int i=0; i < 4; i++){
        int next_x = x + dir[i][0];
        int next_y = y + dir[i][1];
        if (next_x < 0 || next_y < 0 || next_x >= grid.size() || next_y >= grid[0].size()){
            flag = 1;
            continue;
        }
        dfs(grid, visited, next_x, next_y);
    }
}

int main(){
    // 1 处理输入
    int n, m;
    cin >> n >> m;
    vector<vector<int>> grid(n, vector<int>(m, 0));
    int x;
    for(int i=0; i<n; i++){
        for(int j=0; j<m; j++){
            cin >> x;
            if (x) grid[i][j] = 1;
        }
    }
    // 2 处理岛屿数量
    int result = 0;
    vector<vector<bool>> visited(n, vector<bool>(m, false));
    for(int i=0; i<n; i++){
        for(int j=0; j<m; j++){
            if (visited[i][j] == false && grid[i][j] == 1){
                count = 0;
                flag = 0;
                dfs(grid, visited, i, j);
                if (flag == 0)
                    result += count;
            }
        }
    }
    // 3 打印结果
    cout << result;
    return 0;
}

2.4 沉没孤岛

题目描述

给定一个由 1(陆地)和 0(水)组成的矩阵,岛屿指的是由水平或垂直方向上相邻的陆地单元格组成的区域,且完全被水域单元格包围。孤岛是那些位于矩阵内部、所有单元格都不接触边缘的岛屿。

现在你需要将所有孤岛“沉没”,即将孤岛中的所有陆地单元格(1)转变为水域单元格(0)。

输入描述

第一行包含两个整数 N, M,表示矩阵的行数和列数。

之后 N 行,每行包含 M 个数字,数字为 1 或者 0,表示岛屿的单元格。

输出描述
输出将孤岛“沉没”之后的岛屿矩阵。 注意:每个元素后面都有一个空格
输入示例
4 5
1 1 0 0 0
1 1 0 0 0
0 0 1 0 0
0 0 0 1 1
输出示例
1 1 0 0 0
1 1 0 0 0
0 0 0 0 0
0 0 0 1 1

解题思路:

  • 1 从岛屿的四周开始遍历,将遍历过的陆地的visited[i][j]=True。
  • 2 最后直接将visited[i][j]为False的陆地的grid值直接改为0.

2.5 单词搜索

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

示例 1:

输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true

解题思路:

  • 1 与上述的遍历不同,这道题的遍历需要将匹配的数组跟随进行传参和判断。
  • 2 与上述遍历不同,该题的已访问路径只能通过回溯设定。

具体代码:

class Solution {
public:
    bool dfs(vector<vector<char>>& board, int i, int j, string word, int k){
        // 1 匹配成功
        if (k == word.size()) return true;
        // 2 越界或者匹配失败
        if (i < 0 || j < 0 || i >= board.size() || j >= board[0].size() || board[i][j] != word[k]) return false;
        // 3 访问过的路径设置不可访问
        int temp = board[i][j];
        board[i][j] = 0;
        // 4 递归
        bool ans = dfs(board, i+1, j, word, k+1) || dfs(board, i-1, j, word, k+1) ||
                   dfs(board, i, j-1, word, k+1) || dfs(board, i, j+1, word, k+1);
        // 5 回溯
        board[i][j] = temp;
        return ans;
    }

    bool exist(vector<vector<char>>& board, string word) {
        int m = board.size(), n = board[0].size();
        for(int i=0; i<m; i++){
            for(int j=0; j<n; j++){
                if (dfs(board, i, j, word, 0)) return true;
            }
        }
        return false;
    }
};