XJTUSE 数据结构与算法第四次作业——任务1

任务1

  1. 题目

尝试使用图来表示迷宫,并利用图的最小支撑树算法完成迷宫的生成。假如迷宫的大小为n*n,此时可以将迷宫表示成一个n*n 的矩阵。为了使用图解决问题,首先需要将迷宫表示成一个图G,其中迷宫中的每一个单元格对应图G 中的一个顶点,单元格i 和单元格j 之间有共享边时,则对应图G 中顶点i 和顶点j 有边相连。当将迷宫以图的抽象表示出来之后,就可以使用图的算法完成问题的求解了。本次实验中涉及到图数据结构的具体实现,Kruskal算法的实现以及Dijkstra最短路径算法的实现,请按照如下子任务的顺序完成本实验。

该任务的主要内容是完成用图数据结构对迷宫的表示实现,成功表示之后,可以使用如下两个策略生成迷宫:

策略 1:随机擦除 70%的图中的边;

策略 2:随机擦除 50%的图中的边;

针对不同的策略,使用 Dijkstra 算法检测所生成的迷宫从入口(左上角)到出口(右下角)是否有路径可达。对每一个策略可以执行 100 次,给出成功生成迷宫的概率(成功即代表从入口到出口一定有路径可达)。

  1. 数据设计

数据结构包括一个底层的GraphM图,我是使用相邻矩阵实现的无向图,以及一个Cell,指的是图中每一个格子。

首先是GraphM图:

GraphM类包括一个int[][] matrix 表示相邻矩阵;一个int E表示边数;一个private ArrayList<Integer> V表示顶点。

  1. public class GraphM {
  2.     //用相邻矩阵实现无向图
  3.     private ArrayList<Integer> V;//顶点
  4.     private int E;//边数
  5.     private int[][] matrix;//相邻矩阵
  6. }

构造函数接受一个整数参数n,表示图中的节点数,创建一个大小为n×n的二维整数数组matrix来表示图的邻接矩阵,并创建一个大小为n的整数列表V来存储图的节点。

  1.     /**
  2.      * 初始化无向图
  3.      * @param n 结点数
  4.      */
  5.     public GraphM(int n) {
  6.         matrix = new int[n][n];
  7.         V = new ArrayList<Integer>(n);
  8.     }

insertVertex(Integer vertex)方法用于添加节点,首先接受一个整数参数vertex,表示要添加的节点的值,再将节点的值添加到节点列表V中。

insertEdge(int v1, int v2, int weight)方法用于插入边。它接受三个参数,v1和v2分别表示边的起点和终点的下标,weight表示边的权值。在这个方法中,通过将matrix[v1][v2]和matrix[v2][v1]设置为给定的权值,将节点v1和节点v2之间的边添加到邻接矩阵中。由于这是无向图,所以同时也将节点v2和节点v1之间的边添加到邻接矩阵中。同时,边的数量E增加1。

  1.     /**
  2.      * 添加结点
  3.      * @param vertex 结点值
  4.      */
  5.     public void insertVertex(Integer vertex){
  6.         V.add(vertex);
  7.     }
  8.     /**
  9.      * 插入边
  10.      * @param v1 起点下标
  11.      * @param v2 终点下标
  12.      * @param weight 权值,这里1表示相邻,1000表示不相邻
  13.      */
  14.     public void insertEdge(int v1,int v2,int weight) {
  15.         matrix[v1][v2] = weight;
  16.         matrix[v2][v1] = weight;
  17.         E++;//边数+1
  18.     }

Output方法是将邻接矩阵的对应行输出,是为了后续方法实现所实现的方法。

  1.     public int[] output(int row){
  2.         return matrix[row];
  3.     }

还有几个方法并没有用到,不再赘述。

  1.     /**
  2.      * 获得v1->v2的权值
  3.      * @param v1
  4.      * @param v2
  5.      * @return
  6.      */
  7.     public int getWeight(int v1,int v2){
  8.         return matrix[v1][v2];
  9.     }
  10.     /**
  11.      * 获得边的数量
  12.      * @return
  13.      */
  14.     public int getEdgeNum(){
  15.         return E;
  16.     }
  17.     /**
  18.      * 获得结点数
  19.      * @return
  20.      */
  21.     public int getVertexNum(){
  22.         return V.size();
  23.     }

Cell类型指的是每一个格子,具体实现如下:

Cell包括x和y表示格子的位置在第几行第几列,right和down表示这个格子与右面下面的格子是否有边,规定,如果有边,那么对应的值为1,否则为1000.

  1. class Cell {
  2.     private int x; // 格子的位置,在第几行
  3.     private int y; // 第几列
  4.     //1 代表 有边 1000 代表 无边
  5.     private int right;
  6.     private int down;
  7. }

Cell的构造方法是传入x和y,以及各种get和set方法,这里不再赘述,直接贴出代码。

  1. class Cell {
  2.     private int x; // 格子的位置,在第几行
  3.     private int y; // 第几列
  4.     private int flag = Constants.NOTINTREE; // flag,标识格子是否已加入树中
  5.     private Cell father = null// 格子的父亲节点
  6.     //1 代表 有边 1000 代表 无边
  7.     private int right;
  8.     private int down;
  9.     public Cell(int xx, int yy) {
  10.         x = xx;
  11.         y = yy;
  12.     }
  13.     public int getX() {
  14.         return x;
  15.     }
  16.     public void setX(int x) {
  17.         this.x = x;
  18.     }
  19.     public int getY() {
  20.         return y;
  21.     }
  22.     public void setY(int y) {
  23.         this.y = y;
  24.     }
  25.     public int getFlag() {
  26.         return flag;
  27.     }
  28.     public void setFlag(int flag) {
  29.         this.flag = flag;
  30.     }
  31.     public Cell getFather() {
  32.         return father;
  33.     }
  34.     public void setFather(Cell father) {
  35.         this.father = father;
  36.     }
  37.     public int getRight() {
  38.         return right;
  39.     }
  40.     public void setRight(int right) {
  41.         this.right = right;
  42.     }
  43.     public int getDown() {
  44.         return down;
  45.     }
  46.     public void setDown(int down) {
  47.         this.down = down;
  48.     }
  49. }

  1. 算法设计

整个算法是这样一个思路,对于第一问,我们需要生成抽象的图G和一个显示出来的图Puzzle,这个Puzzle实际数据类型是int[][],而图G本质也是邻接矩阵int[][],但是这两个矩阵也并不相同,两者有一定割裂;为了解决消除“边”的操作,我们还需要down和right两个int[][],当里面的数为1时,表示格子相邻,距离为1,当数为1000时表示格子不相邻,距离为1000无穷大。

而整个应用实现也包括前端和后端,前端是根据Puzzle进行绘制的,后端是根据图G生成的,因此考虑分两个类Puzzle和Test分别做后端和前端。

在Puzzle类中包括down和right两个int[][],以及一个图G。

  1. public class Puzzle {
  2.     public GraphM graph;
  3.     //down,right数组存储格子的上下左右四条边
  4.     int[][] down = new int[Constants.GRID_SIZE][Constants.GRID_SIZE];
  5.     int[][] right = new int[Constants.GRID_SIZE][Constants.GRID_SIZE];
  6. }

我们要实现newPuzzle()方法,得到down和right数组以及图G;而getPath()方法会根据实现的图G根据Kruskal算法得到一个List保存迷宫的路径。

newPuzzle()方法概述:

  1. 初始化down和right两个数组为1000.
  2. 根据比例随机down和right两个数组部分数为1.
  3. 创建抽象图G,定义图G的所有顶点,创建图并把顶点添加到图中。
  4. 根据down和right两个数组将边添加到图G中。

newPuzzle()方法详细介绍:

  1. 初始化down和right两个数组为1000.
  1.         //1.数组全1000表示完整格子
  2.         for (int i = 0; i < Constants.GRID_SIZE; i++) {
  3.             for (int j = 0; j < Constants.GRID_SIZE; j++) {
  4.                 down[i][j] = Constants.INNEIGHBOURING;
  5.                 right[i][j] = Constants.INNEIGHBOURING;
  6.             }
  7.         }

  1. 根据比例随机down和right两个数组部分数为1.
  1.         //2.随机down和right两个数组
  2.         randomizeArray(down);
  3.         randomizeArray(right);

  1.     private static void randomizeArray(int[][] array) {
  2.         Random random = new Random();
  3.         int totalCells = Constants.GRID_SIZE * Constants.GRID_SIZE;
  4.         int numZeroes = (int) (Constants.RANDOMPERCENTAGE * totalCells);
  5.         while (numZeroes > 0) {
  6.             int randomRow = random.nextInt(Constants.GRID_SIZE);
  7.             int randomCol = random.nextInt(Constants.GRID_SIZE);
  8.             if (array[randomRow][randomCol] != Constants.NEIGHBOURING) {
  9.                 array[randomRow][randomCol] = Constants.NEIGHBOURING;
  10.                 numZeroes--;
  11.             }
  12.         }
  13.     }

  1. 创建抽象图G,定义图G的所有顶点,创建图并把顶点添加到图中。
  1.         //3.创建抽象的全连接的图
  2.         //定义图的所有顶点
  3.         Integer[] vertexs = new Integer[Constants.GRID_SIZE * Constants.GRID_SIZE];
  4.         for (int i = 0; i < vertexs.length; i++) {
  5.             vertexs[i] = i;
  6.         }
  7.         //创建图
  8.         graph = new GraphM(vertexs.length);
  9.         //添加顶点到图中
  10.         for (Integer vertex : vertexs) {
  11.             graph.insertVertex(vertex);
  12.         }

  1. 根据down和right两个数组将边添加到图G中。
  1.         //4.根据down和right两个数组添加边到图中
  2.         for (int i = 0; i < Constants.GRID_SIZE - 1; i++) {
  3.             for (int j = 0; j < Constants.GRID_SIZE; j++) {
  4.                 //对于down数组
  5.                 if(down[i][j] == Constants.INNEIGHBOURING){
  6.                     graph.insertEdge(i * Constants.GRID_SIZE + j, ( i + 1 ) * Constants.GRID_SIZE + j, Constants.INNEIGHBOURING);
  7.                 }else{
  8.                     graph.insertEdge(i * Constants.GRID_SIZE + j, ( i + 1 ) * Constants.GRID_SIZE + j, Constants.NEIGHBOURING);
  9.                 }
  10.             }
  11.         }
  12.         for (int i = 0; i < Constants.GRID_SIZE; i++) {
  13.             for (int j = 0; j < Constants.GRID_SIZE - 1; j++) {
  14.                if(right[i][j] == Constants.INNEIGHBOURING){
  15.                    graph.insertEdge(i * Constants.GRID_SIZE + j, i * Constants.GRID_SIZE + j + 1, Constants.INNEIGHBOURING);
  16.                }else{
  17.                    graph.insertEdge(i * Constants.GRID_SIZE + j, i * Constants.GRID_SIZE + j + 1, Constants.NEIGHBOURING);
  18.                }
  19.             }
  20.         }

getPath()方法概述:

  1. 构建图的邻接矩阵。
  2. 导入图G的邻接矩阵。
  3. 调用DijkstraAlgorithm类的方法生成对应的路径。

getPath()方法详细讲解:

  1. 构建图的邻接矩阵。
  1.         // 构建图的邻接矩阵表示
  2.         List<List<Integer>> adjacentMatrix = new ArrayList<>();

  1. 导入图G的邻接矩阵。
  1.         // 导入图的链接矩阵
  2.         for (int i = 0; i < Constants.GRID_SIZE * Constants.GRID_SIZE; i++) {
  3.             int[] array = graph.output(i);
  4.             List<Integer> row = new ArrayList<>();
  5.             for (int value : array) {
  6.                 row.add(value);
  7.             }
  8.             adjacentMatrix.add(row);
  9.         }

  1. 调用DijkstraAlgorithm类的方法生成对应的路径。
  1.         // 调用方法
  2.         DijkstraAlgorithm dijkstraAlgorithm = new DijkstraAlgorithm();
  3.         int source = 0;
  4.         List<Integer> Path = dijkstraAlgorithm.getShortestPath(adjacentMatrix, source, Constants.GRID_SIZE * Constants.GRID_SIZE - 1);
  5.         return Path;

Puzzle类中使用了DijkstraAlgorithm类中的方法,Dijkstra算法是一种用于在带权重的图中找到从源节点到目标节点的最短路径的经典算法算法的核心思想是通过不断更新节点之间的距离来逐步找到最短路径。它维护两个列表:一个是距离列表,用于存储从源节点到每个节点的最短距离;另一个是访问标记列表,用于标记节点是否已经被访问过。

算法的步骤如下:

初始化距离列表和访问标记列表。将源节点的距离设置为0,其他节点的距离设置为无穷大,访问标记设置为false。

选择一个未访问的节点中距离最小的节点,并将其标记为已访问。

遍历该节点的所有邻居节点,并更新从源节点到邻居节点的距离。如果通过当前节点到邻居节点的路径距离比之前计算的距离更短,更新距离列表和路径列表。

重复步骤2和步骤3,直到所有节点都被访问过或者没有可达节点。

该实现还提供了一个getShortestPath方法,用于获取从源节点到目标节点的最短路径。它首先调用dijkstra方法获取最短距离列表,然后从目标节点开始沿着最短路径倒序回溯,直到回溯到源节点,以获取完整的最短路径。

  1. import java.util.ArrayList;
  2. import java.util.Collections;
  3. import java.util.List;
  4. public class DijkstraAlgorithm {
  5.     private static final int INF = 500;
  6.     public List<Integer> dijkstra(List<List<Integer>> G, int source) {
  7.         int n = G.size();
  8.         List<Integer> dis = new ArrayList<>(n);
  9.         List<Boolean> vis = new ArrayList<>(n);
  10.         for (int i = 0; i < n; i++) {
  11.             dis.add(INF);
  12.             vis.add(false);
  13.         }
  14.         dis.set(source, 0);
  15.         List<List<Integer>> paths = new ArrayList<>(n); // 存储节点0到每个节点的具体路径
  16.         for (int i = 0; i < n; i++) {
  17.             paths.add(new ArrayList<>());
  18.         }
  19.         paths.get(source).add(source); // 初始路径为只包含起始节点
  20.         for (int i = 0; i < n - 1; i++) {
  21.             int node = -1;
  22.             for (int j = 0; j < n; j++) {
  23.                 if (!vis.get(j) && (node == -1 || dis.get(j) < dis.get(node))) {
  24.                     node = j;
  25.                 }
  26.             }
  27.             for (int j = 0; j < n; j++) {
  28.                 int weight = G.get(node).get(j);
  29.                 if (weight != 0) { // Consider the presence of an edge as weight 1
  30.                     if (dis.get(node) + weight < dis.get(j)) {
  31.                         dis.set(j, dis.get(node) + weight);
  32.                         paths.get(j).clear();
  33.                         paths.get(j).addAll(paths.get(node));
  34.                         paths.get(j).add(j);
  35.                     }
  36.                 }
  37.             }
  38.             vis.set(node, true);
  39.         }
  40.         return dis;
  41.     }
  42.     public List<Integer> getShortestPath(List<List<Integer>> G, int source, int destination) {
  43.         List<Integer> dis = dijkstra(G, source);
  44.         List<Integer> path = new ArrayList<>();
  45.         if (dis.get(destination) == INF) {
  46.             return path;
  47.         }
  48.         int current = destination;
  49.         path.add(current);
  50.         while (current != source) {
  51.             int prev = -1;
  52.             int minWeight = INF;
  53.             for (int neighbor = 0; neighbor < G.size(); neighbor++) {
  54.                 int weight = G.get(neighbor).get(current);
  55.                 if (weight != 0 && dis.get(neighbor) + weight == dis.get(current)) {
  56.                     prev = neighbor;
  57.                     break;
  58.                 }
  59.             }
  60.             if (prev == -1) {
  61.                 return path;
  62.             }
  63.             path.add(prev);
  64.             current = prev;
  65.         }
  66.         Collections.reverse(path);
  67.         return path;
  68.     }
  69. }

Test类继承自JPanel,Test类包括public int padding表示图距边框的距离;public int width表示每一个Cell的宽度;public Cell[][] maze表示储存Cell的格子;public Puzzle puzzle是迷宫;private int numberFailtures表示没有路径的次数;private int numberTry 表示总的实验次数。

  1. public class Test extends JPanel {
  2.     public int padding = Constants.PADDING;
  3.     public int width = (Constants.WIDTH - Constants.PADDING - Constants.PADDING) / Constants.GRID_SIZE;
  4.     public Cell[][] maze;
  5.     public Puzzle puzzle;
  6.     private int numberFailtures = 0;
  7.     private int numberTry = 0;
  8. }

paintComponent是Java Swing框架中的一个方法,它用于在组件上绘制可视化内容。它是在自定义组件中重写的一个方法,用于定义如何绘制组件的外观,paintComponent方法会在组件需要重新绘制时自动调用,允许使用Graphics对象进行绘制操作,以实现自定义的界面效果和可视化内容。

paintComponent方法概述:

  1. 绘制完整的格子。
  2. 使用背景色,根据Puzzle中的数组,在有路径的格子之间画边,把墙抹掉。
  3. 绘制左上角和右下角的出口。
  4. 绘制下边框和右边框
  5. 绘制正确路径。

paintComponent方法详细解释:

  1. 绘制完整的格子。
  1.         super.paintComponent(g);
  2.         // 画NUM*NUM条黑线
  3.         for (int i = 0; i <= Constants.GRID_SIZE; i++) {
  4.             g.drawLine(padding + i * width, padding, padding + i * width,
  5.                     padding + Constants.GRID_SIZE * width);
  6.         }
  7.         for (int j = 0; j <= Constants.GRID_SIZE; j++) {
  8.             g.drawLine(padding, padding + j * width, padding + Constants.GRID_SIZE * width,
  9.                     padding + j * width);
  10.         }

  1. 使用背景色,根据Puzzle中的数组,在有路径的格子之间画边,把墙抹掉。
  1.         // 使用背景色,在有路径的格子之间画边,把墙抹掉
  2.         g.setColor(this.getBackground());
  3.         //根据Puzzle中的数组进行抹边
  4.         for (int i = 0; i < Constants.GRID_SIZE; i++) {
  5.             for (int j = 0; j < Constants.GRID_SIZE; j++) {
  6.                 maze[i][j] = new Cell(i, j);
  7.                 if (puzzle.down[i][j] == Constants.NEIGHBOURING) {
  8.                     maze[i][j].setDown(Constants.NEIGHBOURING);
  9.                 }
  10.                 if (puzzle.down[i][j] == Constants.INNEIGHBOURING) {
  11.                     maze[i][j].setDown(Constants.INNEIGHBOURING);
  12.                 }
  13.                 if (puzzle.right[i][j] == Constants.NEIGHBOURING) {
  14.                     maze[i][j].setRight(Constants.NEIGHBOURING);
  15.                 }
  16.                 if (puzzle.right[i][j] == Constants.INNEIGHBOURING) {
  17.                     maze[i][j].setRight(Constants.INNEIGHBOURING);
  18.                 }
  19.             }
  20.         }
  21.         for (int i = Constants.GRID_SIZE - 1; i >= 0; i--) {
  22.             for (int j = Constants.GRID_SIZE - 1; j >= 0; j--) {
  23.                 if (maze[i][j].getRight() == Constants.NEIGHBOURING) {
  24.                     clearFence(i, j, i, j + 1, g);
  25.                 }
  26.                 if (maze[i][j].getDown() == Constants.NEIGHBOURING) {
  27.                     clearFence(i, j, i + 1, j, g);
  28.                 }
  29.             }
  30.         }

  1.     // 私有方法,目的是抹掉两个格子之间的边
  2.     private void clearFence(int i, int j, int fx, int fy, Graphics g) {
  3.         int sx = padding + ((j > fy ? j : fy) * width),
  4.                 sy = padding + ((i > fx ? i : fx) * width),
  5.                 dx = (i == fx ? sx : sx + width),
  6.                 dy = (i == fx ? sy + width : sy);
  7.         if (sx != dx) {
  8.             sx++;
  9.             dx--;
  10.         } else {
  11.             sy++;
  12.             dy--;
  13.         }
  14.         g.drawLine(sx, sy, dx, dy);
  15.     }

  1. 绘制左上角和右下角的出口。
  1.         // 画左上角的入口
  2.         g.drawLine(padding, padding + 1, padding, padding + width - 1);
  3.         int last = padding + Constants.GRID_SIZE * width;
  4.         // 画右下角出口
  5.         g.drawLine(last, last - 1, last, last - width + 1);

  1. 绘制下边框和右边框
  1.         // 画下边框
  2.         g.setColor(Color.BLACK);
  3.         g.drawLine(padding, last, last, last);
  4.         // 画右边框
  5.         g.drawLine(last, padding, last, last - width);

  1. 绘制正确路径。
  1.         // 画路径
  2.         drawPath(g);

  1.     private void drawPath(Graphics g) {
  2.         g.setColor(Color.red);
  3.         // 已经得到初始路径到最后路径的list,接下来对照相应的格子,再画出来就行
  4.         List<Integer> path = puzzle.getPath();
  5.         for (int i = 0; i < path.size() - 1; i++) {
  6.             int first = path.get(i);
  7.             int second = path.get(i + 1);
  8.             g.drawLine(getCenterX(maze[getRow(first)][getCol(first)]),getCenterY(maze[getRow(first)][getCol(first)]),getCenterX(maze[getRow(second)][getCol(second)]),getCenterY(maze[getRow(second)][getCol(second)]));
  9.         }
  10.     }

由序列数得到对应的行和列。

  1.     // 由序列数得到对应的行和列
  2.     private int getRow(int num) {
  3.         return (num - 1) / Constants.GRID_SIZE;
  4.     }
  5.     private int getCol(int num) {
  6.         return num % Constants.GRID_SIZE;
  7.     }

由格子的行数列数,得到格子中心点的像素XY坐标

  1.     // 由格子的行数列数,得到格子中心点的像素XY坐标
  2.     private int getCenterX(Cell p) {
  3.         return padding + p.getY() * width + width / 2;
  4.     }
  5.     private int getCenterY(Cell p) {
  6.         return padding + p.getX() * width + width / 2;
  7.     }

Count方法,核心就是新生成一个Puzzle,并计算这个Puzzle有没有解,如果无解就令numberFailtures++。

  1.     private void count() {
  2.         //方便统计
  3.         Puzzle p = new Puzzle();
  4.         p.newPuzzle();
  5.         List<Integer> path = p.getPath();
  6.         numberTry++;
  7.         if (path.isEmpty()) {
  8.             numberFailtures++;
  9.         }
  10.     }

Test方法,生成了一个Puzzle并作图,然后反复调用100次count方法并输出迷宫的成功率。

  1.     Test() {
  2.         maze = new Cell[Constants.GRID_SIZE][Constants.GRID_SIZE];
  3.         puzzle = new Puzzle();
  4.         puzzle.newPuzzle();
  5.         for (int i = 0; i < 100; i++) {
  6.             count();
  7.         }
  8.         System.out.println("迷宫成功率为" + (double) (100 - (numberFailtures / numberTry * 100)) + " %");
  9.     }

除此之外,我先为当前应用中很多类需要使用的常量完成定义,这样做更加易于维护,后续修改代码会方便很多。

  1. /**
  2.  * 为当前应用中很多类需要使用的常量完成定义
  3.  * 将逻辑上相关联的一组类所需要的常量定义在一个类中是可以借鉴的,这样易于维护。
  4.  */
  5. public class Constants {
  6.     /** 定义面板的每一行的单元格数 */
  7.     public static final int GRID_SIZE = 40;
  8.     /** 定义面板的大小 */
  9.     public static final int WIDTH = 600;
  10.     /** 定义面板的padding */
  11.     public static final int PADDING = 10;
  12.     /** 在树中 */
  13.     public static final int INTREE = 1;
  14.     /** 不在树中*/
  15.     public static final int NOTINTREE = 0;
  16.     /** 显示在屏幕上的位置 */
  17.     public static final int LX = 450;
  18.     public static final int LY = 100;
  19.     /** 随机消除百分比 */
  20.     public static final double RANDOMPERCENTAGE = 0.7;
  21.     /** 权值,这里1表示相邻,1000表示不相邻 */
  22.     public static final int NEIGHBOURING = 1;
  23.     public static final int INNEIGHBOURING = 1000;
  24. }

  1. 运行结果展示

主函数进行测试:

  1.     public static void main(String[] args) {
  2.         JFrame frame = new JFrame("Maze");
  3.         JPanel test = new Test();
  4.         //下面一行自动调用paintComponent方法
  5.         frame.setContentPane(test);
  6.         frame.setSize(Constants.WIDTH + Constants.PADDING, Constants.WIDTH + Constants.PADDING + Constants.PADDING);
  7.         frame.setLocation(Constants.LX, Constants.LY);
  8.         frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  9.         frame.setVisible(true);
  10.     }

结果如下:
 


 图1  成功结果图


 图2  失败结果图

随机擦除 70%的图中的边时成功几率较大,约为60%-75%

结果如下:


图3  随机擦除 70%的图中的边时迷宫成功率

随机擦除 50%的图中的边时成功几率极小,约为0%-1%

结果如下:


图4  随机擦除 50%的图中的边时迷宫成功率

  1. 总结和收获

通过这个实验,学习了Java Swing框架中paintComponent方法的使用,也复习了图的数据结构,复习了Dijkstra 算法,收获很大。

 

附录:

  1. 任务1
  1. DijkstraAlgorithm算法:
  1. import java.util.ArrayList;
  2. import java.util.Collections;
  3. import java.util.List;
  4. public class DijkstraAlgorithm {
  5.     private static final int INF = 500;
  6.     public List<Integer> dijkstra(List<List<Integer>> G, int source) {
  7.         int n = G.size();
  8.         List<Integer> dis = new ArrayList<>(n);
  9.         List<Boolean> vis = new ArrayList<>(n);
  10.         for (int i = 0; i < n; i++) {
  11.             dis.add(INF);
  12.             vis.add(false);
  13.         }
  14.         dis.set(source, 0);
  15.         List<List<Integer>> paths = new ArrayList<>(n); // 存储节点0到每个节点的具体路径
  16.         for (int i = 0; i < n; i++) {
  17.             paths.add(new ArrayList<>());
  18.         }
  19.         paths.get(source).add(source); // 初始路径为只包含起始节点
  20.         for (int i = 0; i < n - 1; i++) {
  21.             int node = -1;
  22.             for (int j = 0; j < n; j++) {
  23.                 if (!vis.get(j) && (node == -1 || dis.get(j) < dis.get(node))) {
  24.                     node = j;
  25.                 }
  26.             }
  27.             for (int j = 0; j < n; j++) {
  28.                 int weight = G.get(node).get(j);
  29.                 if (weight != 0) { // Consider the presence of an edge as weight 1
  30.                     if (dis.get(node) + weight < dis.get(j)) {
  31.                         dis.set(j, dis.get(node) + weight);
  32.                         paths.get(j).clear();
  33.                         paths.get(j).addAll(paths.get(node));
  34.                         paths.get(j).add(j);
  35.                     }
  36.                 }
  37.             }
  38.             vis.set(node, true);
  39.         }
  40.         return dis;
  41.     }
  42.     public List<Integer> getShortestPath(List<List<Integer>> G, int source, int destination) {
  43.         List<Integer> dis = dijkstra(G, source);
  44.         List<Integer> path = new ArrayList<>();
  45.         if (dis.get(destination) == INF) {
  46.             return path;
  47.         }
  48.         int current = destination;
  49.         path.add(current);
  50.         while (current != source) {
  51.             int prev = -1;
  52.             int minWeight = INF;
  53.             for (int neighbor = 0; neighbor < G.size(); neighbor++) {
  54.                 int weight = G.get(neighbor).get(current);
  55.                 if (weight != 0 && dis.get(neighbor) + weight == dis.get(current)) {
  56.                     prev = neighbor;
  57.                     break;
  58.                 }
  59.             }
  60.             if (prev == -1) {
  61.                 return path;
  62.             }
  63.             path.add(prev);
  64.             current = prev;
  65.         }
  66.         Collections.reverse(path);
  67.         return path;
  68.     }
  69. }

  1. GraphM:
  1. import java.util.ArrayList;
  2. public class GraphM {
  3.     //用相邻矩阵实现无向图
  4.     private ArrayList<Integer> V;//顶点
  5.     private int E;//边数
  6.     private int[][] matrix;//相邻矩阵
  7.     /**
  8.      * 初始化无向图
  9.      * @param n 结点数
  10.      */
  11.     public GraphM(int n) {
  12.         matrix = new int[n][n];
  13.         V = new ArrayList<Integer>(n);
  14.     }
  15.     /**
  16.      * 添加结点
  17.      * @param vertex 结点值
  18.      */
  19.     public void insertVertex(Integer vertex){
  20.         V.add(vertex);
  21.     }
  22.     /**
  23.      * 插入边
  24.      * @param v1 起点下标
  25.      * @param v2 终点下标
  26.      * @param weight 权值,这里1表示相邻,1000表示不相邻
  27.      */
  28.     public void insertEdge(int v1,int v2,int weight) {
  29.         matrix[v1][v2] = weight;
  30.         matrix[v2][v1] = weight;
  31.         E++;//边数+1
  32.     }
  33.     /**
  34.      * 获得v1->v2的权值
  35.      * @param v1
  36.      * @param v2
  37.      * @return
  38.      */
  39.     public int getWeight(int v1,int v2){
  40.         return matrix[v1][v2];
  41.     }
  42.     /**
  43.      * 获得边的数量
  44.      * @return
  45.      */
  46.     public int getEdgeNum(){
  47.         return E;
  48.     }
  49.     /**
  50.      * 获得结点数
  51.      * @return
  52.      */
  53.     public int getVertexNum(){
  54.         return V.size();
  55.     }
  56.     public int[] output(int row){
  57.         return matrix[row];
  58.     }
  59. }

  1. Cell:
  1. class Cell {
  2.     private int x; // 格子的位置,在第几行
  3.     private int y; // 第几列
  4.     private int flag = Constants.NOTINTREE; // flag,标识格子是否已加入树中
  5.     private Cell father = null// 格子的父亲节点
  6.     //1 代表 有边 1000 代表 无边
  7.     private int right;
  8.     private int down;
  9.     public Cell(int xx, int yy) {
  10.         x = xx;
  11.         y = yy;
  12.     }
  13.     public int getX() {
  14.         return x;
  15.     }
  16.     public void setX(int x) {
  17.         this.x = x;
  18.     }
  19.     public int getY() {
  20.         return y;
  21.     }
  22.     public void setY(int y) {
  23.         this.y = y;
  24.     }
  25.     public int getFlag() {
  26.         return flag;
  27.     }
  28.     public void setFlag(int flag) {
  29.         this.flag = flag;
  30.     }
  31.     public Cell getFather() {
  32.         return father;
  33.     }
  34.     public void setFather(Cell father) {
  35.         this.father = father;
  36.     }
  37.     public int getRight() {
  38.         return right;
  39.     }
  40.     public void setRight(int right) {
  41.         this.right = right;
  42.     }
  43.     public int getDown() {
  44.         return down;
  45.     }
  46.     public void setDown(int down) {
  47.         this.down = down;
  48.     }
  49. }

  1. Puzzle:
  1. import java.util.ArrayList;
  2. import java.util.List;
  3. import java.util.Random;
  4. public class Puzzle {
  5.     public GraphM graph;
  6.     //down,right数组存储格子的上下左右四条边
  7.     int[][] down = new int[Constants.GRID_SIZE][Constants.GRID_SIZE];
  8.     int[][] right = new int[Constants.GRID_SIZE][Constants.GRID_SIZE];
  9.     public void newPuzzle() {
  10.         //1.数组全1000表示完整格子
  11.         for (int i = 0; i < Constants.GRID_SIZE; i++) {
  12.             for (int j = 0; j < Constants.GRID_SIZE; j++) {
  13.                 down[i][j] = Constants.INNEIGHBOURING;
  14.                 right[i][j] = Constants.INNEIGHBOURING;
  15.             }
  16.         }
  17.         //2.随机down和right两个数组
  18.         randomizeArray(down);
  19.         randomizeArray(right);
  20.         //3.创建抽象的全连接的图
  21.         //定义图的所有顶点
  22.         Integer[] vertexs = new Integer[Constants.GRID_SIZE * Constants.GRID_SIZE];
  23.         for (int i = 0; i < vertexs.length; i++) {
  24.             vertexs[i] = i;
  25.         }
  26.         //创建图
  27.         graph = new GraphM(vertexs.length);
  28.         //添加顶点到图中
  29.         for (Integer vertex : vertexs) {
  30.             graph.insertVertex(vertex);
  31.         }
  32.         //4.根据down和right两个数组添加边到图中
  33.         for (int i = 0; i < Constants.GRID_SIZE - 1; i++) {
  34.             for (int j = 0; j < Constants.GRID_SIZE; j++) {
  35.                 //对于down数组
  36.                 if(down[i][j] == Constants.INNEIGHBOURING){
  37.                     graph.insertEdge(i * Constants.GRID_SIZE + j, ( i + 1 ) * Constants.GRID_SIZE + j, Constants.INNEIGHBOURING);
  38.                 }else{
  39.                     graph.insertEdge(i * Constants.GRID_SIZE + j, ( i + 1 ) * Constants.GRID_SIZE + j, Constants.NEIGHBOURING);
  40.                 }
  41.             }
  42.         }
  43.         for (int i = 0; i < Constants.GRID_SIZE; i++) {
  44.             for (int j = 0; j < Constants.GRID_SIZE - 1; j++) {
  45.                if(right[i][j] == Constants.INNEIGHBOURING){
  46.                    graph.insertEdge(i * Constants.GRID_SIZE + j, i * Constants.GRID_SIZE + j + 1, Constants.INNEIGHBOURING);
  47.                }else{
  48.                    graph.insertEdge(i * Constants.GRID_SIZE + j, i * Constants.GRID_SIZE + j + 1, Constants.NEIGHBOURING);
  49.                }
  50.             }
  51.         }
  52.     }
  53.     private static void randomizeArray(int[][] array) {
  54.         Random random = new Random();
  55.         int totalCells = Constants.GRID_SIZE * Constants.GRID_SIZE;
  56.         int numZeroes = (int) (Constants.RANDOMPERCENTAGE * totalCells);
  57.         while (numZeroes > 0) {
  58.             int randomRow = random.nextInt(Constants.GRID_SIZE);
  59.             int randomCol = random.nextInt(Constants.GRID_SIZE);
  60.             if (array[randomRow][randomCol] != Constants.NEIGHBOURING) {
  61.                 array[randomRow][randomCol] = Constants.NEIGHBOURING;
  62.                 numZeroes--;
  63.             }
  64.         }
  65.     }
  66.     // 得到实现路径的方法
  67.     public List<Integer> getPath(){
  68.         // 构建图的邻接矩阵表示
  69.         List<List<Integer>> adjacentMatrix = new ArrayList<>();
  70.         // 导入图的链接矩阵
  71.         for (int i = 0; i < Constants.GRID_SIZE * Constants.GRID_SIZE; i++) {
  72.             int[] array = graph.output(i);
  73.             List<Integer> row = new ArrayList<>();
  74.             for (int value : array) {
  75.                 row.add(value);
  76.             }
  77.             adjacentMatrix.add(row);
  78.         }
  79.         // 调用方法
  80.         DijkstraAlgorithm dijkstraAlgorithm = new DijkstraAlgorithm();
  81.         int source = 0;
  82.         List<Integer> Path = dijkstraAlgorithm.getShortestPath(adjacentMatrix, source, Constants.GRID_SIZE * Constants.GRID_SIZE - 1);
  83.         return Path;
  84.     }
  85. }

  1. Test:
  1. import javax.swing.*;
  2. import java.awt.*;
  3. import java.util.List;
  4. public class Test extends JPanel {
  5.     public int padding = Constants.PADDING;
  6.     public int width = (Constants.WIDTH - Constants.PADDING - Constants.PADDING) / Constants.GRID_SIZE;
  7.     public Cell[][] maze;
  8.     public Puzzle puzzle;
  9.     private int numberFailtures = 0;
  10.     private int numberTry = 0;
  11.     Test() {
  12.         maze = new Cell[Constants.GRID_SIZE][Constants.GRID_SIZE];
  13.         puzzle = new Puzzle();
  14.         puzzle.newPuzzle();
  15.         for (int i = 0; i < 300; i++) {
  16.             count();
  17.         }
  18.         System.out.println("迷宫成功率为" + (100 - ((double)numberFailtures / numberTry * 100)) + " %");
  19.     }
  20.     //这是个自动调用的方法,必须设置为public
  21.     public void paintComponent(Graphics g) {
  22.         super.paintComponent(g);
  23.         // 画NUM*NUM条黑线
  24.         for (int i = 0; i <= Constants.GRID_SIZE; i++) {
  25.             g.drawLine(padding + i * width, padding, padding + i * width,
  26.                     padding + Constants.GRID_SIZE * width);
  27.         }
  28.         for (int j = 0; j <= Constants.GRID_SIZE; j++) {
  29.             g.drawLine(padding, padding + j * width, padding + Constants.GRID_SIZE * width,
  30.                     padding + j * width);
  31.         }
  32.         // 使用背景色,在有路径的格子之间画边,把墙抹掉
  33.         g.setColor(this.getBackground());
  34.         //根据Puzzle中的数组进行抹边
  35.         for (int i = 0; i < Constants.GRID_SIZE; i++) {
  36.             for (int j = 0; j < Constants.GRID_SIZE; j++) {
  37.                 maze[i][j] = new Cell(i, j);
  38.                 if (puzzle.down[i][j] == Constants.NEIGHBOURING) {
  39.                     maze[i][j].setDown(Constants.NEIGHBOURING);
  40.                 }
  41.                 if (puzzle.down[i][j] == Constants.INNEIGHBOURING) {
  42.                     maze[i][j].setDown(Constants.INNEIGHBOURING);
  43.                 }
  44.                 if (puzzle.right[i][j] == Constants.NEIGHBOURING) {
  45.                     maze[i][j].setRight(Constants.NEIGHBOURING);
  46.                 }
  47.                 if (puzzle.right[i][j] == Constants.INNEIGHBOURING) {
  48.                     maze[i][j].setRight(Constants.INNEIGHBOURING);
  49.                 }
  50.             }
  51.         }
  52.         for (int i = Constants.GRID_SIZE - 1; i >= 0; i--) {
  53.             for (int j = Constants.GRID_SIZE - 1; j >= 0; j--) {
  54.                 if (maze[i][j].getRight() == Constants.NEIGHBOURING) {
  55.                     clearFence(i, j, i, j + 1, g);
  56.                 }
  57.                 if (maze[i][j].getDown() == Constants.NEIGHBOURING) {
  58.                     clearFence(i, j, i + 1, j, g);
  59.                 }
  60.             }
  61.         }
  62.         // 画左上角的入口
  63.         g.drawLine(padding, padding + 1, padding, padding + width - 1);
  64.         int last = padding + Constants.GRID_SIZE * width;
  65.         // 画右下角出口
  66.         g.drawLine(last, last - 1, last, last - width + 1);
  67.         // 画下边框
  68.         g.setColor(Color.BLACK);
  69.         g.drawLine(padding, last, last, last);
  70.         // 画右边框
  71.         g.drawLine(last, padding, last, last - width);
  72.         // 画路径
  73.         drawPath(g);
  74.     }
  75.     // 私有方法,目的是抹掉两个格子之间的边
  76.     private void clearFence(int i, int j, int fx, int fy, Graphics g) {
  77.         int sx = padding + ((j > fy ? j : fy) * width),
  78.                 sy = padding + ((i > fx ? i : fx) * width),
  79.                 dx = (i == fx ? sx : sx + width),
  80.                 dy = (i == fx ? sy + width : sy);
  81.         if (sx != dx) {
  82.             sx++;
  83.             dx--;
  84.         } else {
  85.             sy++;
  86.             dy--;
  87.         }
  88.         g.drawLine(sx, sy, dx, dy);
  89.     }
  90.     // 由格子的行数列数,得到格子中心点的像素XY坐标
  91.     private int getCenterX(Cell p) {
  92.         return padding + p.getY() * width + width / 2;
  93.     }
  94.     private int getCenterY(Cell p) {
  95.         return padding + p.getX() * width + width / 2;
  96.     }
  97.     // 由序列数得到对应的行和列
  98.     private int getRow(int num) {
  99.         return (num - 1) / Constants.GRID_SIZE;
  100.     }
  101.     private int getCol(int num) {
  102.         return num % Constants.GRID_SIZE;
  103.     }
  104.     private void drawPath(Graphics g) {
  105.         g.setColor(Color.red);
  106.         // 已经得到初始路径到最后路径的list,接下来对照相应的格子,再画出来就行
  107.         List<Integer> path = puzzle.getPath();
  108.         for (int i = 0; i < path.size() - 1; i++) {
  109.             int first = path.get(i);
  110.             int second = path.get(i + 1);
  111.             g.drawLine(getCenterX(maze[getRow(first)][getCol(first)]), getCenterY(maze[getRow(first)][getCol(first)]), getCenterX(maze[getRow(second)][getCol(second)]), getCenterY(maze[getRow(second)][getCol(second)]));
  112.         }
  113.     }
  114.     private void count() {
  115.         //方便统计
  116.         Puzzle p = new Puzzle();
  117.         p.newPuzzle();
  118.         List<Integer> path = p.getPath();
  119.         numberTry++;
  120.         if (path.isEmpty()) {
  121.             numberFailtures++;
  122.         }
  123.     }
  124.     public static void main(String[] args) {
  125.         JFrame frame = new JFrame("Maze");
  126.         JPanel test = new Test();
  127.         //下面一行自动调用paintComponent方法
  128.         frame.setContentPane(test);
  129.         frame.setSize(Constants.WIDTH + Constants.PADDING, Constants.WIDTH + Constants.PADDING + Constants.PADDING);
  130.         frame.setLocation(Constants.LX, Constants.LY);
  131.         frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  132.         frame.setVisible(true);
  133.     }
  134. }

  1. Constants:
  1. /**
  2.  * 为当前应用中很多类需要使用的常量完成定义
  3.  * 将逻辑上相关联的一组类所需要的常量定义在一个类中是可以借鉴的,这样易于维护。
  4.  */
  5. public class Constants {
  6.     /** 定义面板的每一行的单元格数 */
  7.     public static final int GRID_SIZE = 40;
  8.     /** 定义面板的大小 */
  9.     public static final int WIDTH = 600;
  10.     /** 定义面板的padding */
  11.     public static final int PADDING = 10;
  12.     /** 在树中 */
  13.     public static final int INTREE = 1;
  14.     /** 不在树中*/
  15.     public static final int NOTINTREE = 0;
  16.     /** 显示在屏幕上的位置 */
  17.     public static final int LX = 450;
  18.     public static final int LY = 100;
  19.     /** 随机消除百分比 */
  20.     public static final double RANDOMPERCENTAGE = 0.7;
  21.     /** 权值,这里1表示相邻,1000表示不相邻 */
  22.     public static final int NEIGHBOURING = 1;
  23.     public static final int INNEIGHBOURING = 1000;
  24. }
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值