代码随想录二刷|图论4

图论

基础知识

1 无向图

(1)度:一个顶点连n条边就度为n

(2)权

加权无向图:有边长的无向图

(3)通道:两个顶点之间有一些边和点,并且没有重复的边

路:两个顶点之间有一些边和点,并且没有重复的边和点

(4)连通性

1)连通图:任意两点之间都有路相连

2)连通分量:极大连通子图

(连通子图,并且没有包含这个连通子图的连通图)

2 有向图

(1)度

入度:n条边指向顶点则入度为n

出度:从顶点发出n条边则出度为n

(2)权

加权有向图:有边长的无向图

(3)连通性

1)强连通:任意2个点可以相互到达。也就是任意两个点之间都有相互指向的有向路(任何一个点,都有到剩下所有点的有向路)

2)强连通分量:极大强连通子图

(强连通子图,并且没有包含这个强连通子图的强连通图)

3 图的构造

邻接表、邻接矩阵 或者用类

(1)邻接表

1)邻接表 使用 数组 + 链表的方式来表示。 邻接表是从边的数量来表示图,有多少边 才会申请对应大小的链表

cpp 复制代码
// 节点编号从1到n,所以申请 n+1 这么大的数组
vector<list<int>> graph(n + 1); // 邻接表,list为C++里的链表
graph[1].push_back(3)  表示5--->3
//遍历点x能到的所有点i
    for(int i:graph[x])
  • 节点1 指向 节点3 和 节点5
  • 节点2 指向 节点4、节点3、节点5
  • 节点3 指向 节点4
  • 节点4指向节点1

2)邻接表的优点:

  • 对于稀疏图的存储,只需要存储边,空间利用率高
  • 遍历节点连接情况相对容易

3)邻接表的缺点:

  • 检查任意两个节点间是否存在边,效率相对低,需要 O(V)时间,V表示某节点连接其他节点的数量。
  • 实现相对复杂,不易理解

(2)邻接矩阵

1)邻接矩阵 使用 二维数组 来表示图结构。 邻接矩阵是从节点的角度来表示图,有多少节点就申请多大的二维数组

grid[i][j] = 6,表示 节点 i 指向 节点j ,边的权值为6

如果想表示无向图,即:grid[i][j] = 6grid[j][i] = 6,表示节点i 与 节点j相互连通,权值为6

2)邻接矩阵的优点:

  • 表达方式简单,易于理解
  • 检查任意两个顶点间是否存在边的操作非常快
  • 适合稠密图,在边数接近顶点数平方的图中,邻接矩阵是一种空间效率较高的表示方法。

3)邻接矩阵的缺点:

  • 遇到稀疏图,会导致申请过大的二维数组造成空间浪费 且遍历 边 的时候需要遍历整个n * n矩阵,造成时间浪费

(3)类

4 图的遍历方式

  • 深度优先搜索(dfs)
  • 广度优先搜索(bfs)

深度优先搜索

1、dfs要点

(1)搜索方向,是认准一个方向搜,直到碰壁之后再换方向

(2)换方向是撤销原路径,回退一步,改为节点链接的下一个路径(回溯的过程)

2、dfs的举例

假设默认路径

那么接下来考虑下一个方向,先回溯退回5,遍历5的下一步选择接下来就选择5---4

在节点4这里,先选择6,到达终点,再回溯退回4,遍历4的下一步选择,接下来就选择4--3

3、dfs代码(也就是回溯)

cpp 复制代码
void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }
    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表);  递归
        回溯,撤销处理结果
    }
}

4、深度优先搜索的三步走

(1)确定递归返回值和参数

(2)确定终止条件,以及终止条件处做什么

(3)遍历当前节点可以选择的下一个点(遍历递归树的一个层)

5、模板题:所有可达路径

题目描述

给定一个有 n 个节点的有向无环图,节点编号从 1 到 n。请编写一个函数,找出并返回所有从节点 1 到节点 n 的路径。每条路径应以节点编号的列表形式表示。

输入描述

第一行包含两个整数 N,M,表示图中拥有 N 个节点,M 条边

后续 M 行,每行包含两个整数 s 和 t,表示图中的 s 节点与 t 节点中有一条路径

输出描述

输出所有的可达路径,路径中所有节点之间空格隔开,每条路径独占一行,存在多条路径,路径输出的顺序可任意。如果不存在任何一条路径,则输出 -1。

注意输出的序列中,最后一个节点后面没有空格! 例如正确的答案是 1 3 5,而不是 1 3 5 , 5后面没有空格!

#include <bits/stdc++.h>
#include <vector>
using namespace std;
vector<int> path;
vector<vector<int>> res;
void dfs(int point, int n, vector<vector<int>>& graph) {
  if (point == n) {
    res.push_back(path);
    return;
  }
  for (int i = 1; i < graph[point].size(); i++) {
    if (graph[point][i]) {
      path.push_back(i);
      dfs(i, n, graph);
      path.pop_back();
    }
  }
}
int main() {
  int n;
  int m;
  cin >> n >> m;
  vector<vector<int>> graph(n + 1, vector<int>(n + 1, 0));
  for (int i = 0; i < m; i++) {
    int start;
    int end;
    cin >> start >> end;
    graph[start][end] = 1;
  }
  path.push_back(1);
  dfs(1, n, graph);
  if (res.size() == 0) {
    cout << -1 ;
    return 0;
  }
  for (int i = 0; i < res.size(); i++) {
    for (int j = 0; j < res[i].size(); j++) {
      cout << res[i][j];
      if (j < res[i].size()-1)
        cout << " ";
    }
    if (i < res.size()-1)
      cout << "\n";
  }
  return 0;
}

广度优先搜索

1、特点

广搜(bfs)是一圈一圈的搜索过程,和深搜(dfs)是一条路跑到黑然后再回溯

2、只要BFS只要搜到终点一定是一条最短路径

3、代码框架

(1)如何保存遍历到的点:

仅仅需要一个容器,能保存我们要遍历过的元素就行,用队列,用栈,用数组,都是可以的

这里选择用队列

(2)要点

1)用队列进行遍历,每次把队首拿出来,弹出,将队首可以走到的后面的点放进队尾,一直到队空

2)在入队的时候标记visit,因为如果在弹出的时候再标记就会导致一个点重复的加入队列

(3)举例

用一个方格地图,假如每次搜索的方向为 上下左右(不包含斜上方),那么给出一个start起始位置,那么BFS就是从四个方向走出第一步

(例子、图和代码来自代码随想录)

int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 表示四个方向
// grid 是地图,也就是一个二维数组
// visited标记访问过的节点,不要重复访问
// x,y 表示开始搜索节点的下标
void bfs(vector<vector<char>>& grid, vector<vector<bool>>& visited, int x, int y) {
    queue<pair<int, int>> que; // 定义队列
    que.push({x, y}); // 起始节点加入队列
    visited[x][y] = true; // 只要加入队列,立刻标记为访问过的节点
    while(!que.empty()) { // 开始遍历队列里的元素
        pair<int ,int> cur = que.front(); que.pop(); // 从队列取元素
        int curx = cur.first;
        int cury = cur.second; // 当前节点坐标
        for (int i = 0; i < 4; i++) { // 开始想当前节点的四个方向左右上下去遍历
            int nextx = curx + dir[i][0];
            int nexty = cury + dir[i][1]; // 获取周边四个方向的坐标
            if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size()) continue;  // 坐标越界了,直接跳过
            if (!visited[nextx][nexty]) { // 如果节点没被访问过
                que.push({nextx, nexty});  // 队列添加该节点为下一轮要遍历的节点
                visited[nextx][nexty] = true; // 只要加入队列立刻标记,避免重复访问
            }
        }
    }

}

岛屿数量

题干

题目描述

给定一个由 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

输出示例

3

思路:每次遇到一个没有访问过的陆地,就是找到一个新岛,然后把与这个陆地相连的陆地全都标记为isited

无论是dfs还是bfs,都是为了找到与这个陆地相连的陆地

1、深度优先搜索

(1)版本1

没有明确的终止条件,用for循环内的判断条件排除

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

int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 四个方向
void dfs(const vector<vector<int>>& grid, vector<vector<bool>>& visited, int x, int y) {
    for (int i = 0; i < 4; i++) {
        int nextx = x + dir[i][0];
        int nexty = y + dir[i][1];
        if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size()) continue;  // 越界了,直接跳过
        if (!visited[nextx][nexty] && grid[nextx][nexty] == 1) { // 没有访问过的 同时 是陆地的

            visited[nextx][nexty] = true;
            dfs(grid, visited, nextx, nexty);
        }
    }
}

int main() {
    int n, m;
    cin >> n >> m;
    vector<vector<int>> grid(n, vector<int>(m, 0));
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> grid[i][j];
        }
    }
	vector<vector<bool>> visited(n, vector<bool>(m, false));

    int result = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (!visited[i][j] && grid[i][j] == 1) {
                visited[i][j] = true;
                result++; // 遇到没访问过的陆地,+1
                dfs(grid, visited, i, j); // 将与其链接的陆地都标记上 true
            }
        }
    }

    cout << result << endl;
}
(2)版本2

1)无返回值,参数包括图、visited、当前坐标,n和m

然后标记当前点visited[i][j] = 1

2)终止条件:

再判断一个点之前,先判断他是不是要考虑的

水域--不考虑

已经访问---不考虑

越界---不考虑

3)从当前点(i,j)出发,遍历上下左右,因为一开始判断有效性,所以所有点都递归

#include <bits/stdc++.h>
#include <vector>
using namespace std;

int directioni[4] = {0, 1, 0, -1};
int directionj[4] = {1, 0, -1, 0};
void dfs(vector<vector<int>> &visited, vector<vector<int>> &graph, int i, int j,
         int n, int m) {
  if (i < 0 || i >= n || j < 0 || j >= m)
    return;
  if (visited[i][j])
    return;
  if (graph[i][j] == 0)
    return;
  visited[i][j] = 1;
  for (int k = 0; k < 4; k++) {
    int x = directioni[k] + i;
    int y = directionj[k] + j;
    dfs(visited, graph, x, y, n, m);
  }
}

int main() {
  int n;
  int m;
  int sum = 0;
  cin>>n>>m;
  vector<vector<int>> graph(n, vector<int>(m, 0));
  vector<vector<int>> visited(n, vector<int>(m, 0));
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < m; j++) {
      cin >> graph[i][j];
    }
  }
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < m; j++) {
      if (!visited[i][j] && graph[i][j]) {
        sum++;
        dfs(visited, graph, i, j, n, m);
      }
    }
  }
  cout << sum;
  return 0;
}

2、广度优先搜索

(1)把当前找到的第一次出现的陆地作为起点,bfs遍历与他相邻的陆地

每次把队首拿出来,弹出,队首可以走到的后面的点如果是没有访问过的陆地就放进队尾并且标记,一直到队空

(2)在入队的时候标记visit,因为如果在弹出的时候再标记就会导致一个点重复的加入队列

#include <bits/stdc++.h>
#include <vector>
using namespace std;

int n;
int m;
int directioni[4] = {0, 1, 0, -1};
int directionj[4] = {1, 0, -1, 0};
void bfs(vector<vector<int>> graph, vector<vector<int>> &visited, int startx,
         int starty) {
  queue<pair<int, int>> q;
  q.push(make_pair(startx, starty));
  visited[startx][starty] = 1;
  while (!q.empty()) {
    pair<int, int> now = q.front();
    q.pop();
    for (int i = 0; i < 4; i++) {
      int x = now.first + directioni[i];
      int y = now.second + directionj[i];
      if (x < 0 || x >= n || y < 0 || y >= m)
        continue;
      if (!visited[x][y] && graph[x][y]) {
        q.push(make_pair(x, y));
        visited[x][y] = 1;
      }
    }
  }
}
int main() {

  int sum = 0;
  cin >> n >> m;
  vector<vector<int>> graph(n, vector<int>(m, 0));
  vector<vector<int>> visited(n, vector<int>(m, 0));
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < m; j++) {
      cin >> graph[i][j];
    }
  }
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < m; j++) {
      if (!visited[i][j] && graph[i][j]) {
        sum++;
        bfs(graph, visited, i, j);
      }
    }
  }
  cout << sum;

  return 0;
}

岛屿的最大面积

题干

题目描述

给定一个由 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,然后把与这个陆地相连的陆地全都标记为visited并且面积+1

遇到第一个没有访问过的陆地就是遇到新的岛屿

无论是dfs还是bfs,都是为了找到与这个陆地相连的陆地,并且记录这个岛屿的面积

dfs
#include <bits/stdc++.h>
#include <vector>
using namespace std;

int directioni[4] = {0, 1, 0, -1};
int directionj[4] = {1, 0, -1, 0};
void dfs(vector<vector<int>> &visited, vector<vector<int>> &graph, int i, int j,
         int n, int m, int &area) {
  if (i < 0 || i >= n || j < 0 || j >= m)
    return;
  if (visited[i][j])
    return;
  if (graph[i][j] == 0)
    return;
  visited[i][j] = 1;
  area++;
  for (int k = 0; k < 4; k++) {
    int x = directioni[k] + i;
    int y = directionj[k] + j;
    dfs(visited, graph, x, y, n, m,area);
  }
}

int main() {
  int n;
  int m;
  cin >> n >> m;
  vector<vector<int>> graph(n, vector<int>(m, 0));
  vector<vector<int>> visited(n, vector<int>(m, 0));
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < m; j++) {
      cin >> graph[i][j];
    }
  }
  int res = 0;
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < m; j++) {
      if (!visited[i][j] && graph[i][j]) {
        int area = 0;
        dfs(visited, graph, i, j, n, m, area);
        res = max(res, area);
      }
    }
  }
  cout << res;
  return 0;
}
bfs
#include <bits/stdc++.h>
#include <vector>
using namespace std;

int n;
int m;
int directioni[4] = {0, 1, 0, -1};
int directionj[4] = {1, 0, -1, 0};
int bfs(vector<vector<int>> graph, vector<vector<int>> &visited, int startx,
        int starty) {
  queue<pair<int, int>> q;
  q.push(make_pair(startx, starty));
  int area = 1;
  visited[startx][starty] = 1;
  while (!q.empty()) {
    pair<int, int> now = q.front();
    q.pop();
    for (int i = 0; i < 4; i++) {
      int x = now.first + directioni[i];
      int y = now.second + directionj[i];
      if (x < 0 || x >= n || y < 0 || y >= m)
        continue;
      if (!visited[x][y] && graph[x][y]) {
        q.push(make_pair(x, y));
        visited[x][y] = 1;
        area++;
      }
    }
  }
  return area;
}
int main() {

  int res = 0;
  cin >> n >> m;
  vector<vector<int>> graph(n, vector<int>(m, 0));
  vector<vector<int>> visited(n, vector<int>(m, 0));
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < m; j++) {
      cin >> graph[i][j];
    }
  }
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < m; j++) {
      if (!visited[i][j] && graph[i][j]) {
        int t = bfs(graph, visited, i, j);
        res = max(res, t);
      }
    }
  }
  cout << res;

  return 0;
}

孤岛的总面积

题干

题目描述

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

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

输入描述

第一行包含两个整数 N, M,表示矩阵的行数和列数。之后 N 行,每行包含 M 个数字,数字为 1 或者 0。

输出描述

输出一个整数,表示所有孤岛的总面积,如果不存在孤岛,则输出 0。

思路

1、思路1

(1)每次遇到一个没有访问过的陆地,然后把与这个陆地相连的陆地全都标记为visited

(2)在标记岛屿陆地的过程中,如果岛屿有的点x < 0 || x >= n || y < 0 || y >= m,那么就是挨着边界的,isolate标记为0,标注这不是一个孤岛

以bfs为例子

#include <bits/stdc++.h>
#include <vector>
using namespace std;
int n;
int m;
int directioni[4] = {0, 1, 0, -1};
int directionj[4] = {1, 0, -1, 0};
int bfs(vector<vector<int>> graph, vector<vector<int>> &visited, int startx,
        int starty) {
  int isolate = 1;
  queue<pair<int, int>> q;
  q.push(make_pair(startx, starty));
  if (startx == 0 || startx == n - 1 || starty == 0 || starty == m - 1)
    isolate = 0;
  int area = 1;
  visited[startx][starty] = 1;
  while (!q.empty()) {
    pair<int, int> now = q.front();
    q.pop();
    for (int i = 0; i < 4; i++) {
      int x = now.first + directioni[i];
      int y = now.second + directionj[i];
      if (x < 0 || x >= n || y < 0 || y >= m)
        continue;
      if (!visited[x][y] && graph[x][y]) {
        q.push(make_pair(x, y));
        visited[x][y] = 1;
        if (x == 0 || x == n - 1 || y == 0 || y == m - 1)
          isolate = 0;
        area++;
      }
    }
  }
  if (isolate)
    return area;
  else
    return 0;
}
int main() {

  int res = 0;
  cin >> n >> m;
  vector<vector<int>> graph(n, vector<int>(m, 0));
  vector<vector<int>> visited(n, vector<int>(m, 0));
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < m; j++) {
      cin >> graph[i][j];
    }
  }
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < m; j++) {
      if (!visited[i][j] && graph[i][j]) {
        int t = bfs(graph, visited, i, j);
        res += t;
      }
    }
  }
  cout << res;

  return 0;
}

2、思路2(更具有普适性,可以兼容沉默孤岛)

从周边找到陆地然后 通过 dfs或者bfs 将周边靠陆地且相邻的陆地都变成海洋,然后再去重新遍历地图 统计此时还剩下的陆地

(代码随想录)

以dfs为例子

#include <iostream>
#include <vector>
using namespace std;
int dir[4][2] = {-1, 0, 0, -1, 1, 0, 0, 1}; // 保存四个方向
int count; // 统计符合题目要求的陆地空格数量
void dfs(vector<vector<int>>& grid, int x, int y) {
    grid[x][y] = 0;
    count++;
    for (int i = 0; i < 4; i++) { // 向四个方向遍历
        int nextx = x + dir[i][0];
        int nexty = y + dir[i][1];
        // 超过边界
        if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size()) continue;
        // 不符合条件,不继续遍历
        if (grid[nextx][nexty] == 0) continue;

        dfs (grid, nextx, nexty);
    }
    return;
}

int main() {
    int n, m;
    cin >> n >> m;
    vector<vector<int>> grid(n, vector<int>(m, 0));
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> grid[i][j];
        }
    }

    // 从左侧边,和右侧边 向中间遍历
    for (int i = 0; i < n; i++) {
        if (grid[i][0] == 1) dfs(grid, i, 0);
        if (grid[i][m - 1] == 1) dfs(grid, i, m - 1);
    }
    // 从上边和下边 向中间遍历
    for (int j = 0; j < m; j++) {
        if (grid[0][j] == 1) dfs(grid, 0, j);
        if (grid[n - 1][j] == 1) dfs(grid, n - 1, j);
    }
    count = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (grid[i][j] == 1) dfs(grid, i, j);
        }
    }
    cout << count << endl;
}

沉没孤岛

题干

题目描述

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

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

输入描述

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

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

输出描述

输出将孤岛"沉没"之后的岛屿矩阵。 注意:每个元素后面都有一个空格

思路

先从四个边界开始遍历,把所有挨着边界的陆地用dfs/bfs记成2

剩下的1都是孤岛

输出的时候遇到2就输出1,遇到1就输出0,遇到0就输出0

#include <iostream>
#include <vector>
using namespace std;
int dir[4][2] = {-1, 0, 0, -1, 1, 0, 0, 1}; // 保存四个方向
int count; // 统计符合题目要求的陆地空格数量
void dfs(vector<vector<int>>& grid, int x, int y) {
    grid[x][y] = 2;
    
    for (int i = 0; i < 4; i++) { // 向四个方向遍历
        int nextx = x + dir[i][0];
        int nexty = y + dir[i][1];
        // 超过边界
        if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size()) continue;
        // 不符合条件
        if (grid[nextx][nexty] == 0 || grid[nextx][nexty] == 2) continue;

        dfs (grid, nextx, nexty);
    }
    return;
}

int main() {
    int n, m;
    cin >> n >> m;
    vector<vector<int>> grid(n, vector<int>(m, 0));
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> grid[i][j];
        }
    }

    // 从左侧边,和右侧边 向中间遍历
    for (int i = 0; i < n; i++) {
        if (grid[i][0] == 1) dfs(grid, i, 0);
        if (grid[i][m - 1] == 1) dfs(grid, i, m - 1);
    }
    // 从上边和下边 向中间遍历
    for (int j = 0; j < m; j++) {
        if (grid[0][j] == 1) dfs(grid, 0, j);
        if (grid[n - 1][j] == 1) dfs(grid, n - 1, j);
    }

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if(grid[i][j]==1)cout<<0<<" ";
            else if(grid[i][j]==2)cout<<1<<" ";
            else cout<<0<<" ";
        }
        cout<<"\n";
    }
    return 0;
    
}

水流问题

题干

题目描述

现有一个 N × M 的矩阵,每个单元格包含一个数值,这个数值代表该位置的相对高度。矩阵的左边界和上边界被认为是第一组边界,而矩阵的右边界和下边界被视为第二组边界。

矩阵模拟了一个地形,当雨水落在上面时,水会根据地形的倾斜向低处流动,但只能从较高或等高的地点流向较低或等高并且相邻(上下左右方向)的地点。我们的目标是确定那些单元格,从这些单元格出发的水可以达到第一组边界和第二组边界。

输入描述

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

后续 N 行,每行包含 M 个整数,表示矩阵中的每个单元格的高度。

输出描述

输出共有多行,每行输出两个整数,用一个空格隔开,表示可达第一组边界和第二组边界的单元格的坐标,输出顺序任意。

思路

暴力

遍历每一个点,判断是否出发的水可以达到第一组边界和第二组边界

优化

从第一组边界上的节点 逆流而上,将遍历过的节点都标记上

同样从第二组边界的边上节点 逆流而上,将遍历过的节点也标记上

然后两方都标记过的节点就是既可以流第一组边界也可以流第二组边界

逆流而上的过程由dfs和bfs实现

#include <bits/stdc++.h>
#include <vector>
using namespace std;
int n;
int m;
vector<vector<int>> res;
int directioni[4] = {0, 1, 0, -1};
int directionj[4] = {1, 0, -1, 0};
void dfs(vector<vector<int>> &visited, vector<vector<int>> &graph, int i,
         int j) {
  if (i < 0 || i >= n || j < 0 || j >= m)
    return;
  if (visited[i][j])
    return;
  visited[i][j] = 1;
  for (int k = 0; k < 4; k++) {
    int x = directioni[k] + i;
    int y = directionj[k] + j;
    if (x < 0 || x >= n || y < 0 || y >= m)
      continue;
    if (graph[i][j] <= graph[x][y])
      dfs(visited, graph, x, y);
  }
}

int main() {

  cin >> n >> m;
  vector<vector<int>> graph(n, vector<int>(m, 0));
  vector<vector<int>> firstbound(n, vector<int>(m, 0));
  vector<vector<int>> secondbound(n, vector<int>(m, 0));
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < m; j++) {
      cin >> graph[i][j];
    }
  }
  for (int i = 0; i < n; i++) {

    dfs(firstbound, graph, i, 0);
    dfs(secondbound, graph, i, m - 1);
  }
  for (int i = 0; i < m; i++) {
    dfs(firstbound, graph, 0, i);
    dfs(secondbound, graph, n - 1, i);
  }
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < m; j++) {
      if (firstbound[i][j] && secondbound[i][j])
        cout << i << " " << j << "\n";
    }
  }

  return 0;
}

建造最大岛屿

题干

题目描述:

给定一个由 1(陆地)和 0(水)组成的矩阵,你最多可以将矩阵中的一格水变为一块陆地,在执行了此操作之后,矩阵中最大的岛屿面积是多少。

岛屿面积的计算方式为组成岛屿的陆地的总数。岛屿是被水包围,并且通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设矩阵外均被水包围。

输入描述:

第一行包含两个整数 N, M,表示矩阵的行数和列数。之后 N 行,每行包含 M 个数字,数字为 1 或者 0,表示岛屿的单元格。

思路

用一次深搜把每个岛屿的面积记录下来。

1、一次遍历地图,得出各个岛屿的面积,并对岛屿做编号记录。可以使用map记录,key为岛屿编号(岛屿的每个陆地都赋值为key),value为岛屿面积

2、再遍历地图,遍历0的方格(因为要将0变成1),并统计该格子以及周边岛屿面积,将其相邻面积相加在一起,遍历所有 0 之后,就可以得出 选一个0变成1 之后的最大面积

(要注意,对于每个格子,都要做一个set记录已经考虑过的岛屿编号)

#include <bits/stdc++.h>
#include <vector>
using namespace std;
int n;
int m;
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
unordered_map<int, int> mapping;
void dfs(vector<vector<int>> &visited, vector<vector<int>>& graph, int x, int y,
         int key) {
  if (x < 0 || x >= n || y < 0 || y >= m)
    return;
  if (visited[x][y])
    return;
  if (graph[x][y] == 0)
    return;
  visited[x][y] = 1;
  graph[x][y] = key;
  mapping[key]++;
  for (int i = 0; i < 4; i++) {
    int newx = x + dx[i];
    int newy = y + dy[i];
    if (newx < 0 || newx >= n || newy < 0 || newy >= m)
      continue;
    dfs(visited, graph, newx, newy, key);
  }
}
int main() {
  cin >> n >> m;
  vector<vector<int>> graph(n, vector<int>(m, 0));
  vector<vector<int>> visited(n, vector<int>(m, 0));
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < m; j++) {
      cin >> graph[i][j];
    }
  }
  int all_land=1;
  int key = 2;
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < m; j++) {
        if(graph[i][j]==0){
            all_land=0;
        }
      if (!visited[i][j] && graph[i][j] == 1) {
        dfs(visited, graph, i, j, key);
        key++;
      }
    }
  }
  if(all_land){
    cout<<n*m;
    return 0;
  }
  int res = 0;
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < m; j++) {
      if (graph[i][j] == 0) {
        int temp = 1;
        unordered_set<int> used_island;
        for (int k = 0; k < 4; k++) {
          int newx = i + dx[k];
          int newy = j + dy[k];
          if (newx < 0 || newx >= n || newy < 0 || newy >= m)
            continue;
          if (graph[newx][newy]) {
            if (used_island.find(graph[newx][newy]) == used_island.end()) {
              temp += mapping[graph[newx][newy]];
              used_island.insert(graph[newx][newy]);
            }
          }
        }
        res = max(res, temp);
      }
    }
  }
  cout << res;
  return 0;
}

字符串接龙

题干

题目描述

字典 strList 中从字符串 beginStr 和 endStr 的转换序列是一个按下述规格形成的序列:

  1. 序列中第一个字符串是 beginStr。
  2. 序列中最后一个字符串是 endStr。
  3. 每次转换只能改变一个字符。
  4. 转换过程中的中间字符串必须是字典 strList 中的字符串,且strList里的每个字符串只用使用一次。

给你两个字符串 beginStr 和 endStr 和一个字典 strList,找到从 beginStr 到 endStr 的最短转换序列中的字符串数目。如果不存在这样的转换序列,返回 0。

输入描述

第一行包含一个整数 N,表示字典 strList 中的字符串数量。 第二行包含两个字符串,用空格隔开,分别代表 beginStr 和 endStr。 后续 N 行,每行一个字符串,代表 strList 中的字符串。

输出描述

输出一个整数,代表从 beginStr 转换到 endStr 需要的最短转换序列中的字符串数量。如果不存在这样的转换序列,则输出 0。

输入示例

text 复制代码
6
abc def
efc
dbc
ebc
dec
dfc
yhn

输出示例

4

思路

1、建模

字符串看作点,两个字符串之间可以转换则有边

(代码随想录)

2、广度优先搜索

因为求最短路径,广度优先搜索只要能找到路径就一定是最短

3、要记录访问过的字符串:因为是无向图

用map记录访问的字符串,同时记录到当前字符串走过的步数

visit[s]表示走到s字符串的最短步数

4、用set村strlist,便于确定字符串是否属于strlist

#include <bits/stdc++.h>
#include <queue>
#include <string>
#include <unordered_map>
#include <unordered_set>
using namespace std;
int main() {
  int n;
  string start;
  string end;
  unordered_set<string> strlist;
  unordered_map<string, int> visit;
  cin >> n;
  cin >> start >> end;
  for (int i = 0; i < n; i++) {
    string t;
    cin >> t;
    strlist.insert(t);
  }
  queue<string> q;
  q.push(start);
  visit[start] = 1;
  while (!q.empty()) {
    string t = q.front();
    q.pop();
    for (int i = 0; i < t.size(); i++) {
      string next = t;
      for (int j = 0; j < 26; j++) {
        if (next[i] == 'a' + j)
          continue;
        next[i] = 'a' + j;
        if (next == end) {
          visit[next] = visit[t] + 1;
          cout << visit[next];
          return 0;
        }
        if (strlist.find(next) == strlist.end())
          continue;
        if (visit[next])
          continue;
        q.push(next);
        visit[next] = visit[t] + 1;
      }
    }
  }
  cout << 0;
  return 0;
}

有向图的完全可达性

题干

题目描述

给定一个有向图,包含 N 个节点,节点编号分别为 1,2,...,N。现从 1 号节点开始,如果可以从 1 号节点的边可以到达任何节点,则输出 1,否则输出 -1。

输入描述

第一行包含两个正整数,表示节点数量 N 和边的数量 K。 后续 K 行,每行两个正整数 s 和 t,表示从 s 节点有一条边单向连接到 t 节点。

输出描述

如果可以从 1 号节点的边可以到达任何节点,则输出 1,否则输出 -1。

输入示例

4 4
1 2
2 1
1 3
2 4

输出示例

1

思路

遍历所有能到达的点,visit记录走过的点,最后遍历visit

dfs

#include <bits/stdc++.h>
#include <list>
#include <vector>
using namespace std;
void dfs(int n,vector<list<int>> graph,vector<int>& visit,int x){
    if(visit[x])return;
    visit[x]=1;
    for(int i:graph[x]){
        dfs(n,graph,visit,i);
    }
}
int main() {
  int n;
  int k;
  cin >> n >> k;
  vector<list<int>> graph(n + 1);
  vector<int> visit(n + 1, 0);
  for (int i = 0; i < k; i++) {
    int a;
    int b;
    cin >> a >> b;
    graph[a].push_back(b);
  }
  dfs(n,graph,visit,1);
  for (int i = 1; i <= n; i++) {
    if (visit[i] == 0) {
      cout << -1;
      return 0;
    }
  }
  cout << 1;
  return 0;
}

bfs

#include <bits/stdc++.h>
#include <list>
#include <vector>
using namespace std;
int main() {
  int n;
  int k;
  cin >> n >> k;
  vector<list<int>> graph(n + 1);
  vector<int> visit(n + 1, 0);
  for (int i = 0; i < k; i++) {
    int a;
    int b;
    cin >> a >> b;
    graph[a].push_back(b);
  }
  queue<int> q;
  q.push(1);
  visit[1] = 1;
  while (!q.empty()) {
    int t = q.front();
    int next;
    q.pop();
    for (int next:graph[t]) {
      if (visit[next])
        continue;
      visit[next] = 1;
      q.push(next);
    }
  }
  for (int i = 1; i <= n; i++) {
    if (visit[i] == 0) {
      cout << -1;
      return 0;
    }
  }
  cout << 1;
  return 0;
}

岛屿的周长

题干

题目描述

给定一个由 1(陆地)和 0(水)组成的矩阵,岛屿是被水包围,并且通过水平方向或垂直方向上相邻的陆地连接而成的。

你可以假设矩阵外均被水包围。在矩阵中恰好拥有一个岛屿,假设组成岛屿的陆地边长都为 1,请计算岛屿的周长。岛屿内部没有水域。

输入描述

第一行包含两个整数 N, M,表示矩阵的行数和列数。之后 N 行,每行包含 M 个数字,数字为 1 或者 0,表示岛屿的单元格。

输出描述

输出一个整数,表示岛屿的周长。

输入示例

5 5
0 0 0 0 0 
0 1 0 1 0
0 1 1 1 0
0 1 1 1 0
0 0 0 0 0

输出示例

14

思路

遍历(以dfs为例)

(1)参数:ans值的是遍历到当前点(x,y)为止的周长

(2)终止条件:(x,y)越界,(x,y)已经访问过

(3)遍历(x,y)接下来可以去的点

1)如果(x,y)是陆地:

判断(nextx,nexty)是不是界外或水,如果是,ans++

2)(x,y)无论是陆地还是海洋,都进行下一个递归

#include <bits/stdc++.h>
#include <list>
#include <vector>
using namespace std;
int n;
int m;
int directioni[4] = {0, 1, 0, -1};
int directionj[4] = {1, 0, -1, 0};
void dfs(vector<vector<int>> graph, vector<vector<int>> &visit, int x, int y,
         int &ans) {
  if (x < 0 || x >= n || y < 0 || y >= m)
    return;
  if (visit[x][y])
    return;
  visit[x][y] = 1;
  for (int i = 0; i < 4; i++) {
    int nextx = x + directioni[i];
    int nexty = y + directionj[i];
    if (graph[x][y] == 1) {
      if (nextx < 0 || nextx >= n || nexty < 0 || nexty >= m ||
          graph[nextx][nexty] == 0) {
        ans++;
      }
    }
    dfs(graph, visit, nextx, nexty, ans);
  }
}
int main() {

  cin >> n >> m;
  vector<vector<int>> graph(n, vector<int>(m, 0));
  vector<vector<int>> visit(n, vector<int>(m, 0));
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < m; j++) {
      cin >> graph[i][j];
    }
  }
  int ans = 0;
  dfs(graph, visit, 0, 0, ans);
  cout << ans;
  return 0;
}
相关推荐
Flower#13 小时前
【图论】判断图中有环的两种方法及实现
算法·深度优先·图论
CylMK1 天前
浅说图论基础
算法·深度优先·图论
HIT最菜电控2 天前
代码随想录二刷|图论2
图论
Vacant Seat2 天前
图论-课程表
java·数据结构·算法·链表·图论
灰灰学姐3 天前
Floyd算法——有向图
数据结构·算法·图论
Rachela_z3 天前
代码随想录算法训练营第五十七天| 图论03
算法·图论
kill bert4 天前
代码随想录第五十天| 图论理论基础
java·算法·动态规划·图论
Emplace4 天前
ABC394F题解
图论
adam_life4 天前
【http://noi.openjudge.cn/】4.3算法之图论——1538:Gopher II
算法·图论·二分图·匈牙利算法·深搜