并查集应用——生成随机迷宫

    在读《数据结构与算法分析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>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值