在读《数据结构与算法分析Java语言描述一书中》,在并查集章节的最后,作者给出了一个并查集的应用:生成随机迷宫
此处作者给了思路,但是没有给出任何代码,所以自己实现了出来,先上最终效果图:
页面是用的thymeleaf,用表格实现,方块区域与墙壁都是一个单元格,后端准备好样式直接在页面显示。
首先先造一个没有路的迷宫:
相关代码:
public class Item {
//元素所在索引
private Integer index;
//元素宽度
private Integer width;
//元素高度
private Integer height;
//是否显示
private Boolean display;
//背景颜色
private String color;
//css样式,供页面显示
private String style;
//根据各属性生成style
public String generateStyle(){
StringBuilder sb = new StringBuilder();
sb.append("width:").append(width).append("px;");
sb.append("height:").append(height).append("px;");
sb.append("background-color: ").append(color).append(";");
if(!display){
sb.append("visibility:hidden");
}
this.style = sb.toString();
return this.style;
}
/**
* 方格类
*/
public class Square extends Item {
// 上级方块的index, 并查集操作用
private Integer value;
public Square(){
//并查集操作前,初始化为-1
this.value = -1;
}
public Integer getValue() {
return value;
}
public void setValue(Integer value) {
this.value = value;
}
}
/**
* 墙壁类
*/
public class Wall extends Item {
//墙壁分隔的方格所在的index
private Integer squareIndex;
//墙壁分隔的另一个方格所在的index
private Integer anotherSquareIndex;
public Integer getSquareIndex() {
return squareIndex;
}
public void setSquareIndex(Integer squareIndex) {
this.squareIndex = squareIndex;
}
public Integer getAnotherSquareIndex() {
return anotherSquareIndex;
}
public void setAnotherSquareIndex(Integer anotherSquareIndex) {
this.anotherSquareIndex = anotherSquareIndex;
}
}
//墙壁列表(只包括墙壁)
private List<Wall> walls = new ArrayList<>();
//item列表(包括方块和墙壁)
private List<Item> items = new ArrayList<>();
//方块总行数
final int rowCnt = config.getRowCnt();
//方块总列数
final int colCnt = config.getColCnt();
//方块边长
final int edgeLen = config.getEdgeLen();
//总行数(包括墙壁)
final int totalRowCnt = 2 * config.getRowCnt() - 1;
//总列数(包括墙壁)
final int totalColCnt = 2 * config.getColCnt() - 1;
//起点位置下标
final int startIndex = (config.getStartRow() * 2) * totalColCnt + config.getStartCol() * 2;
//终点位置下标
final int endIndex = (config.getEndRow() * 2) * totalColCnt + config.getEndCol() * 2;
int count = 0;
for(int i=0;i<totalRowCnt;i++){
if(i%2 == 0){
//方块和墙壁混合区域
for(int j = 0;j < totalColCnt;j++){
if(j%2==0){
//如果是方块
Square square = new Square();
square.setHeight(edgeLen);
square.setWidth(edgeLen);
square.setColor("black");
square.setIndex(count++);
items.add(square);
} else {
Wall wall = new Wall();
wall.setHeight(edgeLen);
wall.setWidth(1);
wall.setColor("red");
wall.setIndex(count++);
wall.setSquareIndex(wall.getIndex() - 1);
wall.setAnotherSquareIndex(wall.getIndex() + 1);
items.add(wall);
walls.add(wall);
}
}
}else{
//只是墙壁
for(int j=0;j<totalColCnt;j++){
Wall wall = new Wall();
wall.setHeight(1);
wall.setWidth(j%2==0?edgeLen:1);
wall.setColor("red");
wall.setIndex(count++);
wall.setSquareIndex(wall.getIndex() - totalColCnt);
wall.setAnotherSquareIndex(wall.getIndex() + totalColCnt);
items.add(wall);
if(j%2==0){
walls.add(wall);
}
}
}
}
这里分两种情况,偶数行包括方块和墙壁,奇数行只包含墙壁,方块和墙壁的样式不一样。
接着用并查集的搜索、合并方法生成迷宫,这里的思路是:
1.随机选取一面墙,并查看墙壁两边的方格是否连通。
2.如果不连通,拆掉这面墙。
3.如果连通,检查起点和终点是否连通,如果不连通则重复第一步,如果连通则循环结束。
//并查集生成迷宫
while(true){
Random random = new Random();
int i = random.nextInt(walls.size());
Wall wall = walls.get(i);
Integer wallIndex = wall.getSquareIndex();
Integer otherWallIndex = wall.getAnotherSquareIndex();
int i1 = find(wallIndex);
int i2 = find(otherWallIndex);
if(i1 == i2){
int i3 = find(startIndex);
int i4 = find(endIndex);
if(i3 == i4){
break;
}
} else {
wall.setDisplay(false);
union(i1, i2);
walls.remove(wall);
Vertex vertex1 = vMap.get(wallIndex);
Vertex vertex2 = vMap.get(otherWallIndex);
vertex1.addAdj(vertex2);
vertex2.addAdj(vertex1);
}
}
//*****************
//**disjoint sets**
//*****************
//查找
private int find(int x){
Square square = (Square)items.get(x);
if (square.getValue() < 0) {
return x;
} else {
//普通查找
// return find(square.getValue(), items);
//路径压缩查找
square.setValue(find(square.getValue()));
return square.getValue();
}
}
//合并
private void union(int root1, int root2) {
// 普通求并
// s[root2] = root1;
// items.get(root2).setValue(root1);
// //按高度求并
Square square2 = (Square)items.get(root2);
Square square1 = (Square)items.get(root1);
if (square2.getValue() < square1.getValue()) {
square1.setValue(root2);
} else {
if (square1.getValue().intValue() == square2.getValue().intValue()) {
square1.setValue(square1.getValue() - 1);
}
square2.setValue(root1);
}
}
在算法运行期间,被拆掉的墙壁会被置为不显示,随后将每个Item的属性拼接成Stype并返回
// 生成最终样式
List<List<String>> uiStyles = new ArrayList<>();
List<String> uiStyle = null;
for(int i=0;i<items.size();i++){
if(i % totalColCnt == 0){
uiStyles.add(uiStyle);
uiStyle = new ArrayList<>();
}
uiStyle.add(items.get(i).generateStyle());
}
uiStyles.add(uiStyle);
return uiStyles;
最终返回给前端渲染
@GetMapping("execute")
public ModelAndView execute(Map<String, Object> map){
Config config = new Config.Builder().rowCnt(21).colCnt(41).build();
RandomLabyrinth randomLabyrinth = new RandomLabyrinth();
List<List<String>> lists = randomLabyrinth.generateLabyrinth(config);
map.put("itemlist", lists);
return new ModelAndView("labyrinth");
}
Config类里面包含一些可配置的参数
//总行数
private int rowCnt;
//总列数
private int colCnt;
//方块边长
private int edgeLen;
//起点所在行
private int startRow;
//起点所在列
private int startCol;
//终点所在行
private int endRow;
//终点所在列
private int endCol;
页面代码
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<meta charset="UTF-8">
<title>random labyrinth</title>
</head>
<body>
<table style="background-color: black">
<tr th:each="items,itemsStat:${itemlist}">
<td th:each="item,itemStat:${items}" th:style="${item}">
</td>
</tr>
</table>
</body>
</html>