Day 48 总结
- 自己实现中遇到哪些困难
- 今日收获,记录一下自己的学习时间
- 11:40 - 14:30
图论
深度收缩 & 广度搜索
并查集
最小生成树
拓扑排序
最短路径算法
图论基础
- 图
- 二维空间里,多个点之间相互连接
- 图的种类
- 有向图
- 无向图
- 加权
- 度 Degree
- 连接某个节点的边的数量
- 出度,入度
- 连通性
- 节点联通情况
- 连通图
- 任意两个节点存在一条路径
- 强连通图
- 任务两个节点可以相互到达
- 连通分量
- 独立的连通子图(无向图中的极大连通子图)
- 强连通分量
- 有向图中极大强连通子图称之为该图的强连通分量
- 图的构造
- 邻接表
- 数组 + 链表
- 适用于稀疏图, 空间利用率高
- 邻接矩阵
- 二维数组
- 表达简单,检查连接的速度块,适合稠密图
- 邻接表
- 图的遍历方式
- 深度优先 dfs
- 广度优先 bfs
深度优先搜索
- 基本理解
- 搜索方向,路径撤销(回溯)
- 代码框架
void dfs(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本节点所连接的其他节点) {
处理节点;
dfs(图,选择的节点); // 递归
回溯,撤销处理结果
}
}
98. 所有可达路径
题目连接:98. 所有可达路径
题目描述:
给定一个有 n 个节点的有向无环图,节点编号从 1 到 n。请编写一个函数,找出并返回所有从节点 1 到节点 n 的路径。每条路径应以节点编号的列表形式表示。
输入:
N个节点,M个边
s节点 与 t节点 是相连的
输出:
所有从1出发的路径 找出并返回所有从节点 1 到节点 n 的路径
实现思路:
用 N*N 的邻接表保存节点的连接信息,通过dfs收集所有路径。
使用visted变量记录当前访问过哪些节点,用于撤销操作 有向无环,不需要visted记录
使用for循环遍历邻接表中当前节点的行,搜索所有相邻节点
path变量保存临时搜索路径,result变量收集所有路径
代码实现:
java
public class Main {
public static List<Integer> path = new ArrayList<>();
public static List<List<Integer>> result = new ArrayList<>();
public static int[][] graph;
public static void main (String[] args) {
/* code */
Scanner in = new Scanner(System.in);
String[] firstLine = in.nextLine().split(" ");
int N = Integer.valueOf(firstLine[0]);
int M = Integer.valueOf(firstLine[1]);
graph = new int[N+1][N+1];
visited = new boolean[N+1];
for (int i=0; i<M; i++) {
String[] line = in.nextLine().split(" ");
int row = Integer.valueOf(line[0]);
int col = Integer.valueOf(line[1]);
graph[row][col] = 1;
}
path.add(1);
dfs1(1, N);
if (result.size() == 0)
System.out.println(-1);
else {
for (List<Integer> p : result) {
for (int i=0; i<p.size()-1; i++)
System.out.print(p.get(i) + " ");
System.out.println(p.get(p.size()-1));
}
}
}
public static void dfs1 (int node, int N) {
// 到达路径末尾,收集路径
if (node == N) { // 找到从1到N的路径
result.add(new ArrayList<>(path));
return;
}
for (int i=1; i<=N; i++) {
if (graph[node][i] == 0) continue;
path.add(i);
dfs1(i, N);
path.remove(path.size()-1);
}
}
}
99. 岛屿数量
题目连接:99. 岛屿数量
题目描述:
给定一个由 1(陆地)和 0(水)组成的矩阵,你需要计算岛屿的数量。岛屿由水平方向或垂直方向上相邻的陆地连接而成,并且四周都是水域。你可以假设矩阵外均被水包围。
输入:
N行,M列的矩阵
矩阵的具体数字,1代表一个岛,0代表水。
输出:
输出一个整数,表示岛屿的数量。如果不存在岛屿,则输出 0。
实现思路:
使用一个visted矩阵,标记都有哪些岛屿是访问过的。
按序访问每一个未访问过的岛屿,以该岛屿为起始点,进行深度搜索, 本次dfs结束之后,孤岛数量+1
深度搜索:
确定四个方向,上,右,下,左
分别dfs这四个分支
代码实现:
java
public class Main {
public static void main (String[] args) {
/* code */
Scanner in = new Scanner(System.in);
String[] firstLine = in.nextLine().split(" ");
int N = Integer.valueOf(firstLine[0]);
int M = Integer.valueOf(firstLine[1]);
int[][] graph = new int[N][M];
boolean[][] visited = new boolean[N][M];
for (int i=0; i<N; i++) {
for (int j=0; j<M; j++) {
graph[i][j] = in.nextInt();
}
}
// for (int i=0; i<N; i++) {
// System.out.println(Arrays.toString(graph[i]));
// }
int ct = 0;
for (int i=0; i<N; i++) {
for (int j=0; j<M; j++) {
if (graph[i][j] == 1 && !visited[i][j]) {
// System.out.println("start:" + i + " " + j);
dfs(graph, visited,i,j, N, M);
ct++;
}
}
}
System.out.println(ct);
}
public static void dfs(int[][] graph, boolean[][] visited, int x, int y, int N, int M) {
int[] directions = {-1,0, 0,1, 1,0, 0,-1}; // 上右下左
visited[x][y] = true;
for (int i=0; i<4; i++) {
int directX = directions[i*2];
int directY = directions[i*2+1];
if (directX+x >= 0 && directX+x < N && directY+y >= 0 && directY+y < M &&
graph[directX+x][directY+y]==1 && !visited[directX+x][directY+y]) {
// System.out.println("dfs:" + (directX+x) + " " + (directY+y));
dfs(graph, visited, directX+x, directY+y, N, M);
}
}
}
}
广度优先搜索
-
BFS, 求点与点之间最短路径,以起始点为圆心,按圈搜索
-
搜索过程
- 起始点,起始点的四个方向
- 队列保存顺(逆)时针的邻居
javaint 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; // 只要加入队列立刻标记,避免重复访问 } } } }
99. 岛屿数量
题目连接:99. 岛屿数量
题目描述:
给定一个由 1(陆地)和 0(水)组成的矩阵,你需要计算岛屿的数量。岛屿由水平方向或垂直方向上相邻的陆地连接而成,并且四周都是水域。你可以假设矩阵外均被水包围。
输入:
N行,M列的矩阵
矩阵的具体数字,1代表一个岛,0代表水。
输出:
输出一个整数,表示岛屿的数量。如果不存在岛屿,则输出 0。
实现思路:
使用一个visted矩阵,标记都有哪些岛屿是访问过的。
按序访问每一个未访问过的岛屿,以该岛屿为起始点,进行广度搜索, 本次bfs结束之后,孤岛数量+1
广度搜索:
确定四个方向,上,右,下,左
使用一个队列将这个四个岛屿存储起来,然后bfs
代码实现:
java
public class Main {
public static void main (String[] args) {
/* code */
Scanner in = new Scanner(System.in);
String[] firstLine = in.nextLine().split(" ");
int N = Integer.valueOf(firstLine[0]);
int M = Integer.valueOf(firstLine[1]);
int[][] graph = new int[N][M];
boolean[][] visited = new boolean[N][M];
for (int i=0; i<N; i++) {
for (int j=0; j<M; j++) {
graph[i][j] = in.nextInt();
}
}
int ct = 0;
for (int i=0; i<N; i++) {
for (int j=0; j<M; j++) {
if (graph[i][j] == 1 && !visited[i][j]) {
// System.out.println("start:" + i + " " + j);
bfs(graph, visited,i,j, N, M);
ct++;
}
}
}
System.out.println(ct);
}
public static void bfs(int[][] graph, boolean[][] visited, int X, int Y, int N, int M) {
int[] directions = {-1,0, 0,1, 1,0, 0,-1}; // 上右下左
Deque<int[]> queue = new LinkedList<>();
queue.add(new int[]{X, Y});
visited[X][Y] = true;
while (!queue.isEmpty()) {
int[] land = queue.poll();
int x = land[0];
int y = land[1];
for (int i=0; i<4; i++) {
int directX = directions[i*2];
int directY = directions[i*2+1];
if (directX+x >= 0 && directX+x < N && directY+y >= 0 && directY+y < M &&
graph[directX+x][directY+y]==1 && !visited[directX+x][directY+y]) {
queue.add(new int[]{directX+x,directY+y});
visited[directX+x][directY+y] = true;
}
}
}
}
}