知识点
①基于rank优化的并查集,用来检测两点是否相连,提高根节点查找效率
②最小生成树,通过随机权值,生成一个随机迷宫
设计思想
可以初始化一个网格地图,然后逐渐打通其中的墙,这里以5*5网格为例,“ ”表示结点,“#”表示墙;
打通前:
可以看出,这里一共有25个结点,如果要形成迷宫,需要打破24堵结点之间的墙;
创建边Edge类,存储边的起点和终点以及权值,每个结点只和自己在网格地图里上下左右相邻结点之间存在带权边,所以这里一共有5*4*2条带权边,权值随机在0~99999之间,然后存储根据Kruskal算法得到最小生成树(每个结点都与其他结点连通并且树的总权值最小)里的每一条边;根据边集合可以知道需要打通那些结点之间的墙,打通完24堵墙,迷宫生成完毕。
打通后:
具体实现
边Edge类:
主要用来存储结点与结点之间的权值,start起点,end终点,value边的权值
package 自动生成迷宫;
public class Edge{
int start;
int end;
int value;
public Edge(){
}
public Edge(int start,int end,int value){
this.start = start;
this.end = end;
this.value = value;
}
}
并查集Union类:
主要用来检测两个结点是否连通,连接两个结点,查找指定结点的根节点
package 自动生成迷宫;
public class Union {
int count;
int[] parent;//记录根节点
int[] rank;//层数
public Union(int count){
this.count = count;
parent = new int[count];
rank = new int[count];
for(int i=0;i<count;i++){
parent[i] = i;
rank[i] = 1;
}
}
//返回根节点的同时进行路径压缩
public int find(int p){
while(parent[p]!=p){
p = parent[p];
parent[p] = parent[parent[p]];
}
return parent[p];
}
//返回两元素是否相连
public boolean isConnected(int p,int q){
return find(p)==find(q);
}
//连接两元素
public void union(int p,int q){
int pRoot = find(p);
int qRoot = find(q);
if(pRoot == qRoot){
return ;
}
if(rank[pRoot]<rank[qRoot]){
parent[pRoot] = qRoot;
}else if(rank[pRoot]>rank[qRoot]){
parent[qRoot] = pRoot;
}else{
parent[pRoot] = qRoot;
rank[qRoot]++;
}
}
}
地图工具MapUtil类
用来生成迷宫数组,实现过程略复杂,简单的划分可以分为以下几步;
①先将网格地图中最上面横边和最左边纵边涉及到的边添加到list中;
②遍历网格中剩下的结点,将其与相邻左边和相邻上面两个结点相连的两条边添加到list中,完成对网格所有结点带权边的信息存储;
③利用Kruskal得到最小生成树,将树中的每条边添加到map中;
④声明大小为(2*row+1)*(2*col+1)的二维数组,用来存储迷宫信息,值为1代表墙,值为-1代表通道;先对每个结点所在位置留空(网格结点分布在奇数行、奇数列上);然后从网格数组的第2列开始对每个结点右侧的墙进行初始化(最左侧的墙先不管);再对网格地图横向铺满的墙(偶数行)进行初始化;最后对最左侧的纵向墙初始化,完成网格墙效果(未连通状态)
⑤对网格墙进行打通:遍历map,得到需要打通的边edge,通过edge可以得到边起点和边终点的结点编号;如果两点相差1,说明左右连通,否则上下连通;start/列数col得到start在纯结点构成的网格中是第x行,start%列数col得到start在纯结点构成的网格中是第y列;在这里保证了start编号一定比end小,如果是左右连通,array1[2*x+1][2*y+2]置空,如果是上下连通,array1[2*x+2][2*y+1]置空(原因:array1在第④步结束时,已经构成了未连通网格地图,在这里只需要对墙进行打通操作就可以了);返回数组array1得到迷宫信息。
package 自动生成迷宫;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class MapUtil {
Union unionUtil;
ArrayList<Edge> list;
ArrayList<Edge> map;
int row;
int col;
public MapUtil(int row,int col){
this.row = row;
this.col = col;
unionUtil = new Union(row*col);
list = new ArrayList<Edge>();
map = new ArrayList<Edge>();
init();
}
public void init(){
for(int i=0;i<col-1;i++){
list.add(new Edge(i,i+1,(int) (Math.random()*100000)));
}
for(int i=0;i<row-1;i++){
list.add(new Edge(i*col,(i+1)*col,(int) (Math.random()*100000)));
}
for(int i=1;i<row;i++){
for(int j=1;j<col;j++){
list.add(new Edge(i*col+j-1,i*col+j,(int) (Math.random()*100000)));
list.add(new Edge((i-1)*col+j,i*col+j,(int) (Math.random()*100000)));
}
}
Collections.sort(list,new Comparator<Edge>() {
@Override
public int compare(Edge o1, Edge o2) {
if(o1.value<o2.value)
return -1;
else
return 1;
}
});//排序
int count = 0;
//最小生成树
for(Edge edge:list){
int start = edge.start;
int end = edge.end;
if(!unionUtil.isConnected(start, end)){//如果还没相连
unionUtil.union(start, end);//连接
map.add(edge);//将打通的通道存储
count++;
if(count==col*row-1)//生成迷宫完毕
break;
}
}
}
public int[][] getMap(){
int[][] array1 = new int[2*row+1][2*col+1];
//网格留空
for(int i=1;i<2*row+1;i+=2){
for(int j=1;j<2*col+1;j+=2){
array1[i][j] = -1;
}
}
//网格纵向画墙
for(int i=1;i<2*row+1;i+=2){
for(int j=2;j<2*col+1;j+=2){
array1[i][j] = 1;
}
}
//网格横向画墙
for(int i=0;i<2*row+1;i+=2){
for(int j=1;j<2*col+1;j++){
array1[i][j] = 1;
}
}
//边界最左边纵向画墙
for(int i=0;i<2*row+1;i++){
array1[i][0] = 1;
}
// 打印连通前迷宫视图
for(int i=0;i<2*row+1;i++){
for(int j=0;j<2*col+1;j++){
if(array1[i][j]==-1)
System.out.print(" ");
else
System.out.print("#");
}
System.out.println();
}
for(Edge edge:map){
int start = edge.start;
int end = edge.end;
if(Math.abs(start-end)==1){//左右连通
int x = start/col;
int y = start%col;
array1[2*x+1][2*y+2] = -1;
}else{//上下连通
int x = start/col;
int y = start%col;
array1[2*x+2][2*y+1] = -1;
}
}
//打印连通后迷宫视图
for(int i=0;i<2*row+1;i++){
for(int j=0;j<2*col+1;j++){
if(array1[i][j]==-1)
System.out.print(" ");
else
System.out.print("#");
}
System.out.println();
}
return array1;
}
public void print(){
int index = 0;
for(Edge edge:map){
if(index%10==0)
System.out.println();
System.out.print("("+edge.start+","+edge.end+","+edge.value+")");
index ++;
}
}
}
面板GamePanel类:
获取图片并显示迷宫
package 自动生成迷宫;
import java.awt.*;
import javax.swing.*;
public class GamePanel extends JPanel{
int[][] map;
MapUtil mapUtil;
int row,col;
int leftX,leftY;
public GamePanel(int row,int col){
this.row = row;
this.col = col;
leftX = 0;
leftY = 0;
mapUtil = new MapUtil(row, col);
map = mapUtil.getMap();
setPreferredSize(new Dimension((col*2+1)*20,(row*2+1)*20));
}
public void paint(Graphics g){
Image image = Toolkit.getDefaultToolkit().getImage("D:/Game/pic1.png");
for(int i=0;i<2*row+1;i++){
for(int j=0;j<2*col+1;j++){
if(map[i][j]!=-1)
g.drawImage(image, leftX+20*j, leftY+20*i, 20, 20,this);
}
}
}
}
窗体Main类:
package 自动生成迷宫;
import javax.swing.JFrame;
public class Main extends JFrame{
public Main(){
this.getContentPane().add(new GamePanel(20, 30));
pack();
setVisible(true);
}
public static void main(String[] args) {
new Main();
}
}
运行效果:
生成一个20行30列的迷宫
补充说明
墙的素材应该放置在 D://Game路径下,命名为pic1.png;
迷宫可以根据自己需要,在自己指定位置添加入口和出口,修改数组指定位置的值就可以。