数据结构与算法-16高级数据结构_图论(图论基础)

图论基础

1 什么是图

1.1 基础定义

图(Graph)是一个用于描述一组对象之间关系的数学结构。这些对象被称为顶点(Vertex),也称为节点(Node)或点(Point),而对象之间的关系则通过边(Edge)来表示,边也称为链接(Link)或线(Line)。图通常以图解形式描绘为顶点的一组点或环,并通过边的线或曲线连接。

1.2 图的基本术语
  1. 顶点(Vertex)
    • 顶点是图的基本元素,它们代表图中的对象或实体。在图形表示中,顶点通常以点或圆圈的形式出现。
    • 一个图G的顶点集合通常用V(G)表示,其中每个元素都是图中的一个顶点。
  2. (Edge)
    • 边是连接图中两个顶点的线段或弧线,表示顶点之间的某种关系或连接。
    • 在无向图中,边没有方向,连接的两个顶点是对称的。
    • 在有向图中,边具有方向,从一个顶点指向另一个顶点,表示一种单向关系。
    • 图的边集合通常用E(G)表示,其中每个元素都是一条边,由它所连接的两个顶点定义。
  3. (Degree)
    • 一个顶点的度是指与该顶点直接相连的边的数量。
    • 在无向图中,顶点的度就是与其相连的边的数量。
    • 在有向图中,顶点的度可以细分为入度(指向该顶点的边的数量)和出度(从该顶点出发的边的数量)。
  4. 路径(Path)
    • 路径是图中顶点和边交替出现的一个序列,其中序列中的每对连续顶点都由一条边相连。
    • 路径的起点和终点可以是同一个顶点,此时称为环(Cycle)。
    • 路径的长度是指路径中边的数量。
  5. 连通性(Connectivity)
    • 在无向图中,如果图中任意两个顶点之间都存在路径,则称该图为连通图。
    • 在有向图中,如果对于每一对顶点u和v,都存在从u到v的路径(或反之),则称该图为强连通图。
    • 如果一个图不是连通的(或强连通的),但它可以分成几个连通(或强连通)的子图,则这些子图称为连通分量(或强连通分量)。
  6. 子图(Subgraph)
    • 如果图G’的顶点集合V’是G的顶点集合V的子集,且G’的边集合E’中的每条边都是G的边集合E中的边,并且与V’中的顶点相关联,则称G’是G的子图。
  7. (Cycle)
    • 环是一种特殊的路径,它的起点和终点是同一个顶点,并且除了起点和终点外,路径中的顶点和边都不重复。
  8. 权重(Weight)
    • 在带权图中,每条边都关联一个权重值,这个值可以表示两个顶点之间关系的某种度量,如距离、成本、时间等。
  9. 邻接性(Adjacency)
    • 如果两个顶点由一条边相连,则称它们是相邻的。在无向图中,邻接是对称的;在有向图中,邻接是有方向的。
1.3 图的分类

一、按边有无方向分类

  1. 无向图(Undirected Graph)

    • 定义:图中的边没有方向,即如果顶点v到顶点w有一条边,那么顶点w到顶点v也存在同一条边。
    • 特点:边表示顶点之间的对称关系。
    • 示例:社交网络中两个人的好友关系,通常视为无向的,因为一个人将另一个人视为好友,则另一个人也将前者视为好友。

    在这里插入图片描述

  2. 有向图(Directed Graph)

    • 定义:图中的边具有方向,即如果顶点v到顶点w有一条边,但并不意味着顶点w到顶点v也存在同一条边。
    • 特点:边表示顶点之间的非对称关系,有向边通常带有箭头,表示方向。
    • 示例:微博中用户之间的关注关系,是单向的,用户A关注用户B,并不意味着用户B也关注用户A

    在这里插入图片描述

二、按节点和边的类别数量分类

在这里插入图片描述

  1. 同构图(Homogeneous Graph)
    • 定义:图中只有一种类型的节点和一种类型的边。
    • 特点:结构简单,易于处理和分析。
    • 示例:传统的社交网络图,其中所有节点都代表用户,所有边都代表用户之间的关系(如好友关系)。
  2. 异构图(Heterogeneous Graph)
    • 定义:图中存在多种类型的节点和/或多种类型的边。
    • 特点:结构复杂,但能够更准确地表示现实世界中的复杂关系。
    • 示例:知识图谱,其中节点可能代表不同类型的实体(如人、地点、组织等),边可能代表不同类型的关系(如出生地、工作于、隶属于等)。

三、按边是否带有权重分类

在这里插入图片描述

  1. 无权图(Unweighted Graph)
    • 定义:图中的边没有权重,仅表示顶点之间是否存在关系。
    • 特点:简单直观,适用于不需要考虑边的重要性或强度的场景。
    • 示例:简单的社交网络图,其中边仅表示两个人是否是好友,而不考虑他们之间关系的紧密程度。
  2. 有权图(Weighted Graph)
    • 定义:图中的边带有权重,权重表示边的重要性或强度。
    • 特点:能够更准确地表示顶点之间的关系,适用于需要考虑边的重要性或强度的场景。
    • 示例:交通网络图,其中边表示道路,权重可能表示道路的长度、行驶时间或交通流量等。

2 图的表示

2.1 邻接矩阵

什么是邻接矩阵

邻接矩阵是一个二维数组(矩阵),其大小为n×n,其中n是图中顶点的数量。矩阵中的元素a[i][j]表示顶点i与顶点j之间的连接关系。根据图的不同类型(如无向图、有向图、有权图等),a[i][j]的取值和含义也会有所不同。

  • 对于无向图,如果顶点i与顶点j相连,则a[i][j]和a[j][i]都为1(或边的权重,如果图是有权图的话);如果顶点i与顶点j不相连,则a[i][j]和a[j][i]都为0。由于无向图的邻接矩阵是对称的,因此在实际存储时,可以只存储上(或下)三角矩阵的元素,以节省空间。
  • 对于有向图,如果顶点i到顶点j有一条有向边,则a[i][j]为1(或边的权重);如果顶点i到顶点j没有有向边,则a[i][j]为0。此时,邻接矩阵不一定是对称的。
  • 对于有权图,无论是有向还是无向,a[i][j]的值都表示顶点i与顶点j之间边的权重。如果顶点i与顶点j不相连,则a[i][j]通常用一个特殊的值来表示,如无穷大(∞)或某个足够大的数,以便在算法中区分不相连的顶点对。

示例说明:

假设有一个无向图G,其顶点集合为V={A, B, C, D},边集合为E={(A,B), (A,C), (B,C), (B,D)}。则图G的邻接矩阵可以表示为:

ABCD
A0110
B1011
C1100
D0100

在这个邻接矩阵中,元素a[i][j]为1表示顶点i与顶点j相连,为0表示不相连。由于是无向图,所以矩阵是对称的。

优缺点

优点

  1. 直观易懂:通过矩阵的形式,可以直观地看出图中顶点之间的连接关系。
  2. 便于计算:在邻接矩阵中,可以方便地通过索引来访问和修改顶点之间的连接关系。

缺点

  1. 空间复杂度高:对于大型稀疏图来说,邻接矩阵会浪费大量的存储空间来存储不存在的边。
  2. 访问效率低:在稀疏图中,访问某个顶点的所有邻接点时需要遍历整行(或整列),效率较低。
2.2 邻接表

什么是邻接表

邻接表的基本思想是为图中的每个顶点维护一个单链表,链表中存储的是与该顶点直接相连的其他顶点。对于无向图而言,由于边是双向的,所以每个顶点的邻接链表中需要包含所有与之相连的其他顶点;而对于有向图,邻接链表则只包含该顶点的出边所指向的顶点。

邻接表基础实现思想:

  1. 顶点存储:通常,我们可以使用一个一维数组来存储图中的顶点信息。数组的每个元素对应图中的一个顶点,其索引值可以作为顶点的标识符。
  2. 邻接链表:对于每个顶点,都维护一个单链表,链表中的节点表示与该顶点直接相连的其他顶点。链表节点的结构一般包含至少两个部分:一个存储邻接顶点在顶点数组中的索引(或直接存储邻接顶点对象),另一个是指向下一个链表节点的指针。
  3. 头指针数组:为了方便地访问每个顶点的邻接链表,我们可以使用一个额外的数组来存储每个顶点邻接链表的头指针。这样,我们就可以通过顶点的索引直接找到其对应的邻接链表。

示例说明:

假设有一个无向图G,其顶点集合为V={A, B, C, D},边集合为E={(A,B), (A,C), (B,C), (B,D)}。使用邻接表来表示该图的结构如下:

顶点邻接链表
AB → C
BA → C → D
CA → B
DB

在这个示例中,每个顶点都对应一个邻接链表,链表中存储与该顶点直接相连的其他顶点(通过顶点在数组中的索引表示)。例如,顶点A的邻接链表包含顶点B和C,表示顶点A与顶点B和C相连。

优缺点

优点

  1. 节省空间:对于稀疏图而言,邻接表能够显著减少存储空间的浪费,因为它只存储实际存在的边和顶点信息。
  2. 添加和删除边方便:在邻接表中添加或删除边通常只需要在对应的邻接链表中添加或删除节点,操作的时间复杂度较低。
  3. 便于查找顶点的邻接点:通过邻接表,我们可以快速地找到与给定顶点直接相连的所有顶点。

缺点

  1. 查找边较慢:在邻接表中查找两个顶点之间是否存在边可能需要遍历其中一个顶点的邻接链表,时间复杂度较高。
  2. 表示有向图时可能不够直观:对于有向图而言,邻接表只能表示顶点的出边,如果需要表示入边,则需要额外的数据结构(如逆邻接表)。
2.3 代码示例

在这里插入图片描述

2.3.1 邻接矩阵
public class MatrixGraph {// 顶点数量private int V;// 邻接矩阵private int[][] adjMatrix;// 构造函数,初始化顶点数量和邻接矩阵public MatrixGraph(int V) {this.V = V;adjMatrix = new int[V][V];}// 添加边// 对于无向图,你还需要调用addEdge(v, w);// 对于有权图,你可以将int[][] adjMatrix改为Integer[][],并将addEdge中的值设置为边的权重public void addEdge(int v, int w) {adjMatrix[v][w] = 1; // 对于无权图,我们使用1表示存在边,0表示不存在边// 注意:对于有向图,我们不需要adjMatrix[w][v] = 1;}// 打印邻接矩阵public void printGraph() {for (int i = 0; i < V; i++) {for (int j = 0; j < V; j++) {System.out.print(adjMatrix[i][j] + " ");}System.out.println();}}// 主函数,用于测试Graph类public static void main(String[] args) {MatrixGraph g = new MatrixGraph(4);g.addEdge(1, 2);g.addEdge(1, 5);g.addEdge(2, 4);g.addEdge(3, 1);g.addEdge(4, 3);System.out.println("邻接矩阵为:");g.printGraph();}
}
2.3.2 邻接表
package cn.zxc.demo.leetcode_demo.advanced_data_structure.graph;import java.util.ArrayList;
import java.util.List;public class LinkGraph {private int V; // 图的顶点数private List<List<Integer>> adjList; // 邻接表// 构造函数public LinkGraph(int V) {this.V = V;adjList = new ArrayList<>(V);for (int i = 0; i < V; i++) {adjList.add(new ArrayList<>());}}// 添加边// 对于无向图,需要调用addEdge(v, w)和addEdge(w, v)public void addEdge(int v, int w) {adjList.get(v).add(w); // 在顶点v的邻接链表中添加顶点w}// 打印图(仅打印邻接表)public void printGraph() {for (int i = 0; i < V; i++) {System.out.print("顶点 " + i + " 的邻接顶点: ");List<Integer> list = adjList.get(i);for (int neighbor : list) {System.out.print(neighbor + " ");}System.out.println();}}// 主函数,用于测试Graph类public static void main(String[] args) {LinkGraph g = new LinkGraph(6);g.addEdge(1, 2);g.addEdge(1, 5);g.addEdge(2, 4);g.addEdge(3, 1);g.addEdge(4, 3);System.out.println("图的邻接表表示:");g.printGraph();}
}

3 图的遍历

3.1 Dfs(深度优先遍历)

基本思想

深度优先搜索算法从根节点(或选定的起始节点)开始,尽可能深地搜索树的分支,直到达到叶子节点。然后,它回溯到前一个节点,继续搜索下一个分支。这个过程一直进行到所有节点都被访问为止。如果图中存在未访问的节点,算法会选择其中一个作为新的起始节点,并重复上述过程。

深度优先搜索的原理是递归或迭代实现回溯。递归方式更为直观和常用,它使用函数调用栈来隐式地维护一个节点栈;迭代方式则需要显式地维护一个栈来存储待访问的节点。

实现步骤

  1. 初始化
    • 对于递归实现,初始化通常意味着设置起始节点和必要的辅助数据结构(如访问标记数组)。
    • 对于迭代实现,还需要初始化一个栈来存储待访问的节点。
  2. 搜索过程
    • 递归实现中,算法通过递归调用自身来遍历节点的所有未访问子节点。
    • 迭代实现中,算法不断地从栈中取出节点,并遍历其所有未访问的邻接节点,将未访问的邻接节点加入栈中。
  3. 回溯
    • 当一个节点的所有邻接节点都被访问过,且没有未访问的分支时,算法会回溯到该节点的父节点,继续搜索其他分支。
  4. 结束条件
    • 当所有节点都被访问过,或者栈为空且没有未访问的节点时,算法结束。

优缺点

优点

  1. 内存消耗小:与广度优先搜索相比,深度优先搜索的空间复杂度较低,特别是在树或稀疏图中。
  2. 实现简单:深度优先搜索的实现相对简单,只需要使用栈(隐式或显式)和访问标记数组。
  3. 适用于深度探索:在需要深度探索的场景中(如迷宫问题、生成树的构造等),深度优先搜索非常有效。

缺点

  1. 难以寻找最优解:在需要找到最优解的场景中(如最短路径问题),深度优先搜索可能不是最佳选择,因为它会深入探索一个分支而忽略其他可能更优的分支。
  2. 可能陷入无限循环:在非连通图或包含环的图中,如果算法设计不当,可能会导致无限循环。因此,需要设置适当的终止条件或使用访问标记数组来避免重复访问节点。

代码示例

在这里插入图片描述

package cn.zxc.demo.leetcode_demo.advanced_data_structure.graph;/*** 有向图深度优先遍历* 1、使用邻接矩阵存储图结构* 2、对邻接矩阵进行深度优先遍历*      从 第一个 顶点开始遍历,往下遍历,如果遇到新的顶点,则继续往下遍历,直到*/
public class Dfs {// 顶点个数 0 ~ (v - 1)private int v;// 邻接矩阵private int[][] graph;// a点 到 b点 的最小步数private int  MIN_STEP = Integer.MAX_VALUE;public Dfs(int v) {this.v = v;this.graph = new int[v][v];// 为每一个顶点赋值1,例如 0-0 1-1 2-2 3-3for (int i = 0; i < v; i++) {this.graph[i][i] = 1;}}// 初始化邻接矩阵// 输入的二维数组,表示的是可以联通的顶点 例如:{{1,2},{1,5},{2,4},{3,1},{4,3}}// {1,2} 表示顶点1和顶点2可以联通public void init(int[][] inits){for (int[] init : inits) {if (init[0] > v || init[1] > v)throw new RuntimeException("顶点个数超出范围");graph[init[0]][init[1]] = 1;}}// 通过深度搜索的方式,判断是否存在从startX 到 endX 的路径public boolean find_dfs(int startX, int endX){boolean[] visited = new boolean[v];visited[startX] = true;return dfs(startX, endX, visited);}/*** 递归实现深度搜索* @param startX 起点* @param endX 终点* @param visited 访问过的顶点【可以防止当图出现环时不断递归(死循环--栈溢出)】* @return*/private boolean dfs(int startX, int endX,boolean[] visited){// 到达目标地点if (startX == endX){return true;}// 由于是有向图,所以直接以起点为横坐标遍历起点可以到达的所有顶点for (int i = 0; i < v; i++) {// 如果可以联通,并且没有被访问过if (graph[startX][i] == 1 && !visited[i]){visited[i] = true;if (dfs(i, endX, visited)){return true;}visited[i] = false;}}return false;}// 通过深度搜索的方式,查找到从startX 到 endX 的最小步数public Integer getMinStep(int startX, int endX){boolean[] visited = new boolean[v];visited[startX] = true;return dfs(startX, endX, 0, visited);}/*** 递归实现深度搜索* @param startX 起点* @param endX 终点* @param step 步长* @param visited 访问过的顶点【可以防止当图出现环时不断递归(死循环--栈溢出)】* @return*/public Integer dfs(int startX, int endX, int step,boolean[] visited){if (startX == endX){if (step + 1 < MIN_STEP){MIN_STEP = step + 1;}return MIN_STEP;}for (int i = 0; i < v; i++) {if (graph[startX][i] == 1 && !visited[i]){visited[i] = true;dfs(i, endX,step + 1, visited);visited[i] = false;}}return MIN_STEP;}public static void main(String[] args) {int[][] inits = {{1,2},{1,5},{2,4},{3,1},{4,3}};Dfs dfs = new Dfs(6);dfs.init(inits);System.out.println(dfs.find_dfs(5,3));System.out.println(dfs.getMinStep(1,5));}
}
3.2 Bfs(广度优先遍历)

基本思想

广度优先搜索(Breadth-First Search, BFS)从根节点(或选定的起始节点)开始,逐层地向外扩展搜索,即先访问起始节点的所有相邻节点,然后再访问这些节点的相邻节点,依此类推,直到找到目标节点或遍历完所有节点。

实现步骤

  1. 初始化
    • 创建一个队列,用于存储待访问的节点。
    • 创建一个访问标记数组或集合,用于记录哪些节点已被访问过。
    • 将起始节点加入队列,并标记为已访问。
  2. 搜索过程
    • 当队列不为空时,执行以下步骤:
      • 从队列中取出一个节点。
      • 访问该节点(如打印节点值、进行某种计算等)。
      • 遍历该节点的所有邻接节点:
        • 如果邻接节点未被访问过,则将其加入队列,并标记为已访问。
  3. 结束条件
    • 当队列为空时,算法结束。此时,所有可达的节点都已被访问过。

优缺点

优点

  1. 找到最短路径:在无权图中,广度优先搜索能够找到从起始节点到目标节点的最短路径(按边的数量计算)。
  2. 实现简单:广度优先搜索的实现相对简单,只需要使用队列和访问标记数组。
  3. 适用于层次遍历:广度优先搜索特别适用于需要按层次遍历的场景,如二叉树的层序遍历。

缺点

  1. 空间复杂度较高:在最坏情况下(即图为完全图时),广度优先搜索需要存储大量的节点,导致空间复杂度较高。
  2. 不适用于有权图:在有权图中,广度优先搜索找到的路径可能不是最短路径(按边的权重和计算)。

代码示例

在这里插入图片描述

package cn.zxc.demo.leetcode_demo.advanced_data_structure.graph;import java.util.concurrent.ArrayBlockingQueue;/*** 有向图广度搜索* 基础说明:使用邻接矩阵存储有向图* 搜索步骤:* 1、创建一个队列,将起点加入队列* 2、从队列中取出一个点,判断是否是终点,如果是则返回,否则将点加入已访问集合,将点邻接点加入队列* 3、重复步骤2,直到队列为空*/
public class Bfs {// 顶点数private int v;// 邻接矩阵private int[][] adjMatrix;public Bfs(int v){this.v = v;this.adjMatrix = new int[v][v];// 为每一个顶点赋值1,例如 0-0 1-1 2-2 3-3for (int i = 0; i < v; i++) {this.adjMatrix[i][i] = 1;}}// 初始化邻接矩阵// 输入的二维数组,表示的是可以联通的顶点 例如:{{1,2},{1,5},{2,4},{3,1},{4,3}}// {1,2} 表示顶点1和顶点2可以联通public void init(int[][] inits){if (inits == null || inits.length > v){throw new RuntimeException("输入的二维数组不合法");}for (int[] init : inits) {adjMatrix[init[0]][init[1]] = 1;}}/*** 判断 起点 到 终点 是否存在路径* @param src 起点* @param dest 终点* @return* @throws InterruptedException*/public boolean find_bfs(int src, int dest) throws InterruptedException {boolean[] visited = new boolean[v];return bfs(src,dest,visited);}/*** 广度搜索* @param src 起点* @param dest 终点* @param visited 访问过的顶点* @return* @throws InterruptedException*/public boolean bfs(int src, int dest,boolean[] visited) throws InterruptedException {if (src == dest){return true;}ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(v);queue.put(src);visited[src] = true;while (!queue.isEmpty()){Integer poll = queue.poll();if (poll == dest){return true;}for (int i = 0; i < v; i++) {if (adjMatrix[poll][i] == 1 && !visited[i]){queue.put(i);visited[i] = true;}}}return false;}public static void main(String[] args) throws InterruptedException {int[][] inits = {{1,2},{1,5},{2,4},{3,1},{4,3}};Bfs dfs = new Bfs(6);dfs.init(inits);System.out.println(dfs.find_bfs(1,3));}
}

4 例题

解救美女1:有一天,小美和你去玩迷宫。但是方向感不好的小美很快就迷路了,你得知后便去解救无助的小美,你已经弄清楚了迷宫的地图,现在你要知道从你当前位置出发你是否能够达到小美的位置?

在这里插入图片描述

思路

使用邻接矩阵表示迷宫地图

1:表示地图上的障碍物,0表示有路可走邻接矩阵:

邻接矩阵:

0(你) 0 1 0

0 0 0 0

0 0 1 0

0 1 0(小美) 0

0 0 0 1

4.1 是否能够到达

使用BFS遍历实现

package cn.zxc.demo.leetcode_demo.advanced_data_structure.graph;import java.util.concurrent.ArrayBlockingQueue;/*** 有一天,小美和你去玩迷宫。但是方向感不好的小美很快就迷路了,你得知后便去解救无助的小美,你已经弄清楚了迷宫的地图,现在你要知道从你当前位置出发你是否能够达到小美的位置?* 起点默认为 {0, 0}*/
public class MazeBfsDemo {// 地图:邻接矩阵private int[][] map;// 可以走动的方向private int[][] dir = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};/*** 初始化迷宫地图* @param weight 宽* @param height 高* @param obstacle 障碍点*/public MazeBfsDemo(int x, int y, int[][] obstacle) {this.map = new int[x][y];for (int[] ints : obstacle) {this.map[ints[0]][ints[1]] = 1;}}/*** 使用Bfs找到目标地点* @param destX 目标地点的x坐标* @param destY 目标地点的y坐标* @return* @throws InterruptedException*/public boolean find(int destX,int destY) throws InterruptedException {boolean[][] visited = new boolean[map.length][map[0].length];return find(0,0,destX,destY,visited);}private boolean find(int srcX, int srxY, int destX, int destY, boolean[][] visited) throws InterruptedException {if (srcX == destX && srxY == destY){return true;}// 将第一个节点入队ArrayBlockingQueue<int[]> queue = new ArrayBlockingQueue<>(map.length * map[0].length);queue.put(new int[]{srcX, srxY});visited[srcX][srxY] = true;while (!queue.isEmpty()){// 弹出节点int[] cur = queue.poll();// 到达目标点则返回trueif (cur[0] == destX && cur[1] == destY){return true;}// 获取弹出的节点的四个方向 判断是否可以走,可以走则将节点入队for (int i = 0; i < dir.length; i++) {int nextX = cur[0] + dir[i][0];int nextY = cur[1] + dir[i][1];if (nextX >= 0 && nextX < map.length && nextY >= 0 && nextY < map[0].length && map[nextX][nextY] == 0 && !visited[nextX][nextY]){queue.put(new int[]{nextX, nextY});visited[nextX][nextY] = true;}}}return false;}public static void main(String[] args) throws InterruptedException {int[][] obstacle = {{0, 2}, {2, 2}, {3, 1}, {4, 3}};MazeBfsDemo mazeBfsDemo = new MazeBfsDemo(5, 4, obstacle);System.out.println(mazeBfsDemo.find(0, 2));}
}
4.2 获取最短路径

使用DFS遍历实现

package cn.zxc.demo.leetcode_demo.advanced_data_structure.graph;import java.util.ArrayList;
import java.util.Arrays;/*** 获取最短路径* 有一天,小美和你去玩迷宫。但是方向感不好的小美很快就迷路了,你得知后便去解救无助的小美,你已经弄清楚了迷宫的地图,现在你要知道从你当前位置出发你是否能够达到小美的位置?* 起点默认为 {0, 0}*/
public class MazeDfsDemo {// 地图:邻接矩阵private int[][] map;// 可以走动的方向private int[][] dir = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};// 最短步数private int minStep = Integer.MAX_VALUE;// 记录路径private ArrayList<String> path;/*** 初始化迷宫地图* @param weight 宽* @param height 高* @param obstacle 障碍点*/public MazeDfsDemo(int x, int y, int[][] obstacle) {this.map = new int[x][y];for (int[] ints : obstacle) {this.map[ints[0]][ints[1]] = 1;}}/*** 查找所有可能到达的路径,并记录最短路径* @param destX 目标点x* @param destY 目标点y*/public void find(int destX,int destY) {if (map[destX][destY] == 1) {System.out.println("该点为障碍点,无法到达");return;}boolean[][] visited = new boolean[map.length][map[0].length];ArrayList<String> tempPath = new ArrayList<>();find(0, 0, destX, destY, 0, visited,tempPath);}/*** 查找所有可能到达的路径,并记录最短路径* @param srcX 起点x* @param srcY 起点y* @param destX 目标点x* @param destY 目标点y* @param step 步数* @param visited 节点访问标识* @param tempPath 临时路径*/private void find(int srcX, int srcY, int destX, int destY, int step, boolean[][] visited,ArrayList<String> tempPath) {// 到达目标点if (srcY == destY && srcX == destX){// 当前步数小于最小步数 ,记录最小步数 & 当前路径if (step < minStep){minStep = step;path = new ArrayList<>(tempPath);}return;}// 每个节点走4个方向for (int i = 0; i < dir.length; i++) {int nextX = srcX + dir[i][0];int nextY = srcY + dir[i][1];// 判断当前方向是否可以走if (nextX < 0 || nextX >= map.length || nextY < 0 || nextY >= map[0].length || visited[nextX][nextY] || map[nextX][nextY] == 1){continue;}// 可以走:记录路径 & 标识节点tempPath.add("(" + nextX + "," + nextY + ")");visited[nextX][nextY] = true;find(nextX, nextY, destX, destY, step + 1, visited,tempPath);// 回溯:移除路径 & 节点标识tempPath.remove(tempPath.size() - 1);visited[nextX][nextY] = false;}}public static void main(String[] args) {int[][] obstacle = {{0, 2}, {2, 2}, {3, 1}, {4, 3}};MazeDfsDemo mazeDfsDemo = new MazeDfsDemo(5, 4, obstacle);mazeDfsDemo.find(3, 2);System.out.println(Arrays.toString(mazeDfsDemo.path.toArray()));}
}

[(1,0), (2,0), (3,0), (4,0), (4,1), (4,2), (3,2)]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/398207.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024国赛Word论文模板【一键生成式操作】

一、比赛介绍 该竞赛创办于1992年&#xff0c;每年一届&#xff0c;是首批列入“高校学科竞赛排行榜”的19项竞赛之一。2023年&#xff0c;来自全国及美国、澳大利亚、马来西亚的1685所院校/校区、59611队(本科54158队、专科5453队)、近18万人报名参赛。 而今年的国赛马上就要…

【CTF | WEB】001、攻防世界WEB题目之backup

文章目录 backup题目描述:解题思路&#xff1a;解题过程&#xff1a; backup 题目描述: X老师忘记删除备份文件&#xff0c;他派小宁同学去把备份文件找出来,一起来帮小宁同学吧&#xff01; 进入题目后显示&#xff1a; 解题思路&#xff1a; 在进行网站安全检查时&#xf…

网络协议四 物理层,数据链路层

从这一节开始学习 五层模型。学习方法是从最底层物理层开始学习 七层模型 五层模型 各个层用的协议&#xff0c;以及加上协议后的称谓 各个层的作用 应用层&#xff1a;可以认为是原始数据&#xff0c;该数据称为 报文&#xff0c;用户数据。 运输层&#xff1a;也叫传输层&am…

全网超详细攻略——LVS原理详解及部署

目录 一、LVS原理 1.LVS简介 2.LVS结构 3.IP负载均衡技术 4.LVS相关术语 二、LVS负载均衡四种工作模式 1.LVS-DR模式 2.LVS-NAT模式 3.LVS-TUN模式&#xff08;了解&#xff09; 4.FULL-NAT模式&#xff08;了解&#xff09; 三、LVS负载均衡十种调度算法 四、LVS部…

米思奇安装——Mac版本

米思奇安装——Mac版本 1.下载 访问米思奇官网https://mixly.org/bnu-maker/mixl2.0rc 打开官网后在首页点击导航栏的软件平台&#xff0c;选择Mixly离线版 点击Mixly2.0RC4发布下载。 进入百度网盘分享的文件&#xff0c;选择Mac一键更新版本&#xff0c;等待下载完成。 …

尚品汇-ES(三十一)

目录&#xff1a; &#xff08;1&#xff09;封装搜索相关实体对象 &#xff08;2&#xff09;搜索接口封装 &#xff08;3&#xff09;在service-list-client模块添加远程接口 &#xff08;1&#xff09;封装搜索相关实体对象 搜索参数实体&#xff1a;SearchParam 搜索参…

第七节 流编辑器sed(stream editor)(7.1)

一,sed简介 sed是一种流编辑器,处理时,把当前处理的行存储在临时缓冲区中,称为模式空间,接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕,接着处理下一行,这样不断重复,直到文件末尾,文件内容并没有改变 二,sed的语法 2,1,基本语法 sed options ... […

AI学习记录 - gpt如何进行token化,理论知识,以GPT2为举例

AI学习记录已经发了十几篇&#xff0c;大佬们可以看看&#xff0c;如果有帮助动动小手点赞 token入门版&#xff0c;有空会更新具体代码操作 GPT4当中&#xff0c;我们提问问题是按照token进行扣费的&#xff0c;那到底什么是token&#xff1f; 在不同的语言模型当中&#x…

gradio之进度条

输出控件显示进度&#xff0c;进度结束显示控件结果 import gradio as gr import timedef slowly_reverse(word, progressgr.Progress()):progress(0, desc"Starting")time.sleep(1)progress(0.05)new_string ""for letter in progress.tqdm(word, desc&…

C++ 特性之vector详解 + 联合opencv使用

C 特性之vector详解 联合opencv使用 在C中&#xff0c;遍历vector并删除元素需要小心处理迭代器失效的问题。通常推荐的方法是使用迭代器进行遍历&#xff0c;并在需要删除元素时使用erase函数。这里给出一个示例代码&#xff0c;演示如何安全地遍历vector并删除特定条件的元…

计算机毕业设计 家电销售展示平台 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

智能识别,2024年SD卡数据恢复软件的智能进化

除了手机之外现在有不少的设备还是依靠SD卡来存储数据&#xff0c;比如相机、摄像头、无人机等。有的时候会因为一些意外的情况导致数据丢失&#xff0c;那是真的丢失了吗&#xff1f;大部分情况还是可以依靠sd卡数据恢复工具来找回这些“消失”的数据哦。 1.福昕数据恢复 链…

python正则表达式以及re模块的运用完成文本处理(搜索、匹配、替换等文本操作)

1.正则表达式 正则表达式是一种强大的文本处理工具&#xff0c;用于搜索、匹配、替换等文本操作。 2.通过re模块实现正则表达式的操作 Python中的re模块是Python的标准库之一&#xff0c;它提供了对正则表达式的支持。正则表达式是一种强大的文本处理工具&#xff0c;用于搜…

AI赋能招聘:效率与公平的双重提升

一、引言&#xff1a;AI赋能招聘新纪元 在21世纪的数字化浪潮中&#xff0c;人工智能&#xff08;AI&#xff09;技术以前所未有的速度渗透到社会经济的各个领域&#xff0c;深刻地改变着我们的生活方式与工作模式。人力资源管理&#xff0c;作为企业战略的重要组成部分&#…

[C++][opencv]基于opencv实现photoshop算法色相和饱和度调整

【测试环境】 vs2019 opencv4.8.0 【效果演示】 【核心实现代码】 HSL.hpp #ifndef OPENCV2_PS_HSL_HPP_ #define OPENCV2_PS_HSL_HPP_#include "opencv2/core.hpp" using namespace cv;namespace cv {enum HSL_COLOR {HSL_ALL,HSL_RED,HSL_YELLOW,HSL_GREEN,HS…

在IIS上部署ASP.NET Core Web API和Blazor Wasm详细教程

前言 前段时间我们完成了七天.NET 8 操作 SQLite 入门到实战的开发系列教程&#xff0c;有不少同学留言问如何将项目发布部署到IIS上面运行。本篇文章我们就一起来讲讲在IIS上部署ASP.NET Core Web API和Blazor Wasm。 前提条件 安装.NET Core SDK https://dotnet.microsoft…

【学习笔记】Day 10

一、进度概述 1、《地震勘探原理》第三章 二、详情 3.1 野外工作概述 主要介绍地上与海上两种情况下的测量方法&#xff0c;这里不做详解&#xff0c;需要就看书。 其中简况分为&#xff1a;试验工作&#xff0c;生产工作过程&#xff0c;干扰波调查&#xff0c;干扰…

allegro PCB设计心得笔记(四) -- 显示坐标原点和更改默认产品选项

一、修改坐标原点 Allegro PCB设计过程中&#xff0c;有时需要修改坐标原点&#xff0c;但是PCB文件不显示坐标原点&#xff0c;无法确认已修改的坐标原点是否已经修改好。 显示PCB原点的设置方法如下&#xff1a; Setup -> Design Parameter Editor&#xff0c;如下图所示&…

[C++][opencv]基于opencv实现photoshop算法图像剪切

【测试环境】 vs2019 opencv4.8.0 【效果演示】 【核心实现代码】 //图像剪切 //参数&#xff1a;src为源图像&#xff0c; dst为结果图像, rect为剪切区域 //返回值&#xff1a;返回0表示成功&#xff0c;否则返回错误代码 int imageCrop(InputArray src, OutputArray dst,…

8.3.数据库基础技术-关系代数

并&#xff1a;结果是两张表中所有记录数合并&#xff0c;相同记录只显示一次。交&#xff1a;结果是两张表中相同的记录。差&#xff1a;S1-S2,结果是S1表中有而S2表中没有的那些记录。 笛卡尔积&#xff1a;S1XS2,产生的结果包括S1和S2的所有属性列&#xff0c;并且S1中每条记…