使用并查集生成一个迷宫

Posted GoldenaArcher

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用并查集生成一个迷宫相关的知识,希望对你有一定的参考价值。

使用并查集生成一个迷宫

之前在做笔记的时候就想……嗯……大概能写点什么有趣的东西出来加强一下记忆?

于是就整出了这个,然后发现对于 setTimeout 以及 setTimeInterval 的应用还不行啊,晚点继续折腾一下。

并查集部分的笔记在这里:并查集(Data Structure for Disjoint Sets),LC 解题部分在这里:LC 200, 721,684 并查集解法,总体来说并查集是一个实现非常简单的算法结构,理解起来也不是非常的困难。

并查集的核心代码依旧不变:

export default class UnionFind 
  constructor() 
    this.rank = ;
    this.parents = ;
  

  makeSet(x) 
    this.parents[x] = x;
    this.rank[x] = 0;
  

  findSet(x) 
    if (this.parents[x] !== this.parents[this.parents[x]]) 
      this.parents[x] = this.findSet(this.parents[this.parents[x]]);
    

    return this.parents[x];
  

  link(x, y) 
    if (this.rank[x] > this.rank[y]) 
      this.parents[y] = x;
     else 
      this.parents[x] = y;
      if (this.rank[x] === this.rank[y]) 
        this.rank[y] = this.rank[y] + 1;
      
    
  

  union(x, y) 
    this.link(this.findSet(x), this.findSet(y));
  

绘图部分用 CSS 来搞定:

const renderBoard = () => 
  return board.map(([up, down, left, right], i) => 
    const classNames = ["uf__cell"];
    if (!up) classNames.push("uf__cell-up");
    if (!down) classNames.push("uf__cell-down");
    if (!left) classNames.push("uf__cell-left");
    if (!right) classNames.push("uf__cell-right");

    return <div className=classNames.join(" ") key=i></div>;
  );
;

其中数组是一个二维数组 是的我没有用三维数组,当然三维数组也可以用,说不定会更简单一些……?,大概结构如下:

const board = [
  // up,  down,  left,  right
  [false, false, false, false],
  [false, false, false, false],
  [false, false, false, false],
  // ...
];

中间的 4 个 boolean 代表的是四个方向是否与其他格子进行联通,如果没有联通那么就使用 css 绘制 border。

绘制之后大概如下:

double border 的问题我倒是有想过解决,但是搜了一下直接使用 margin-left: -1px 不太好用,我就放弃了。2b 项目写多了 css 是真的苦手……

本来如果用定时器重新每隔一段时间渲染一下会比较简单,不过我还在挣扎……嗯……无限循环了……所以这里还是使用手动操作去解决了。

不管怎么说看到 A-Star 的时候自动渲染这部分早晚得解决 OTL,现在就先狗着吧。

这个原理就是随机选取一个结点并且将其移除,这个想法是被 LC684. 冗余连接 启发的,写完这道题之后让我觉得好像画一个迷宫就是这么简单,用这道题的变种就行了。

这部分代码如下:

const getAdjacentNode = (node) => 
  let left = null;

  if (node - 1 >= 0 && node % row !== 0) 
    left = node - 1;
  

  let right = null;

  if (node + 1 < row * col && node % row !== row - 1) 
    right = node + 1;
  
  const up = node - row < 0 ? null : node - row;
  const down = node + row >= row * col ? null : node + row;

  return [up, down, left, right];
;

const pickNode = () => 
  while (true) 
    const node = getRandomIntInclusive(0, row * col - 1),
      neightbors = getAdjacentNode(node);

    const i = getRandomIntInclusive(0, 4);

    const neighbor = neightbors[i];
    if (!neighbor) continue;

    if (uf.findSet(node) !== uf.findSet(neighbor)) 
      return [node, neighbor, i];
    
  
;

const removeNode = () => 
  // there is a path exist already, no need to remove node anymore
  if (uf.findSet(0) === uf.findSet(row * col - 1)) return;

  const [node, neighbor, i] = pickNode();

  uf.union(node, neighbor);

  setBoard((prevState) => 
    const newBoard = _.cloneDeep(prevState);

    switch (i) 
      case 0:
        newBoard[node][0] = true;
        newBoard[neighbor][1] = true;
        break;
      case 1:
        newBoard[node][1] = true;
        newBoard[neighbor][0] = true;
        break;
      case 2:
        newBoard[node][2] = true;
        newBoard[neighbor][3] = true;
        break;
      case 3:
        newBoard[node][3] = true;
        newBoard[neighbor][2] = true;
        break;
      default:
        break;
    

    return newBoard;
  );
;

为了创建一种伪随机性,这里 随机 抽取了两个元素:

  1. 数组中的元素

  2. 被选中元素的邻居

    因为没有使用二维数组,所以这里的检查会稍微多一点,否则会出现某行最后一个元素与下一行第一个元素被打通的情况

如果二者不相连的话,那么就返回,否则就继续在 while(true) 这个循环里面继续呆着。移除结点就是进行 union 的操作,并且进行 CSS 的修正。

实现后的效果如下:

省略 n 步后:

一些可以做的优化

大概是提速……?如果结点很多的话可能会卡到白屏,50x50 的渲染还挺快:

100x100 的就容易崩,当然,等个几分钟还是能出来的:

主要是 React 还是在网页条件下跑的,直接就把浏览器给 block 了。

其次是目前没有加 cycle detection 的检查

大概是样本太小了,10x10 的图画了好多个没画出一个有圈的,大的图大概有,不过我……不太想找。

感兴趣的源码可以在这里巴拉:https://github.com/GoldenaArcher/algo-visualization,具体可视化的做多少就……有一个做一个了……?

以上是关于使用并查集生成一个迷宫的主要内容,如果未能解决你的问题,请参考以下文章

[hdu-1272]小希的迷宫 并查集

HDU 1272 小希的迷宫(乱搞||并查集)

第一个算法项目 | JS实现并查集迷宫算法Demo学习

小希的迷宫-并查集

小希的迷宫(并查集)

1272小希的迷宫 并查集