union-find实现网页版的迷宫

前段时间研究了下并查集(union-find)算法。此算法是用来处理不相交集合的。这种数据模型在计算机和网络的世界里它是不可或缺的。在网络中各个网域间形成不相交集合;在社交网络中,社交圈之间形成不相交集合。

union-find的应用于测试无向图节点间的连通性,因此可以用来随机生成一个迷宫模型。迷宫又一个个小格子构成。把它想象成一个一个的房间,我们的目的是使入口房间和出口房间最终可以连通。为了保证路径复杂性,每次随机选择两个相邻的‘房间’判断它们是否相同,如果不通就打破‘墙体’。

 下面是网页版迷宫的效果图:

迷宫的绘制采用svg技术实现,整理来说实现此效果要分3个步骤:

第一步:建立坐标系,根据规模(nxn)创建房间对象并对房间进行编号;这里我是以左上角为原点,横向为x轴,纵向为y轴,并逐行进行编号;

第二步:建立nxn个不相交集,确定入口和出口的位置;这里左上角为入口的位置、右下角是出口;

第三步:利用算法模型生成墙体;

整个代码的结构分为3块:

1、迷宫对象(maze.js);

2、房间对象(Cell.js);

3、算法模型(DSets.js)

html的核心代码:

页面的结构很简单,主要就是一个svg元素,并绘制一个矩形区域当作边界;

<div class="mazewrap">
            <div class="box">
                <svg xmlns="http://www.w3.org/2000/svg" id="mazesvg" width="500" height="500" viewBox="20 20 460 460" onload="maze.init(evt);">
                    <g id="group">
                        <rect x="40" y="40" width="420" height="420" 
                            style="stroke:black;stroke-width:1;fill:Wheat;"/>
                    </g>
                </svg>
            </div>
        </div>

svg加载完毕即进行迷宫的初始化;

maze.js核心代码:

function Maze(){
    this.initParams = {
        rows: 21,
        columns: 21,
        cellsize: 20
    }
}

Maze.prototype.randomMaze = function(){
    var r = this.initParams.rows;
    var c = this.initParams.columns;
    var dsets = new DSets(r*c);
    //debugger;
    //初始化房间对象
    var cells = new Array(r*c);
    for(var i = 0; i < r; ++i)
        for(var j = 0; j < c; ++j){
            var cell = new Cell(i, j, r, c);
            cells[cell.cellno] = cell;
        }
    //初始化集合对象
    for(i = 0; i < cells.length; ++i){
        dsets.makeSet(cells[i].cellno);
    }
    //打通墙体
    while(dsets.findSet(0) != dsets.findSet(r*c-1)){
        //1 选择任意一个房间
        var cellno = GetRandomNum(0, r*c);
        var cell = cells[cellno];
        //2 找到所有相邻的房间
        var neighbors = cell.getNeighbors();
        //3 随机选择一个相邻的房间,并判断两者的连通性
        var ri = GetRandomNum(0, neighbors.length);
        var ncellno = neighbors[ri];
        var ncell = cells[ncellno];
        if(dsets.findSet(cellno) != dsets.findSet(ncellno)){
            dsets.unionSet(cellno, ncellno);
            //4-1 拆墙
            var group = svgRoot.getElementById('group');
            var which = this.getWhichWall(cellno, ncellno),wall;
            switch(which){
            case 1:  //右墙
                cell.right = 0;
                ncell.left = 0;
                try{
                    wall = svgRoot.getElementById(cell.getRightWall());
                    if(wall)
                        group.removeChild(wall);
                }catch(e){
                    console&&console.log(svgRoot.getElementById(cell.getRightWall()),cell.getRightWall());
                }
            break;
            case -1: 
                cell.left = 0;
                ncell.right = 0;
                try{
                    wall = svgRoot.getElementById(cell.getLeftWall());
                    if(wall)
                        group.removeChild(wall);
                }catch(e){
                    console&&console.log(svgRoot.getElementById(cell.getLeftWall()), cell.getLeftWall());
                }
            break;
            case 2:  //下墙
                cell.bottom = 0;
                ncell.top = 0;
                try{
                    wall = svgRoot.getElementById(cell.getBottomWall());
                    if(wall)
                        group.removeChild(wall);
                }catch(e){
                    console&&console.log(svgRoot.getElementById(cell.getBottomWall()), cell.getBottomWall());
                }
            break;
            case -2:
                cell.top = 0;
                ncell.bottom = 0;
                try{
                    wall = svgRoot.getElementById(cell.getTopWall());
                    if(wall)
                        group.removeChild(wall);
                }catch(e){
                    console&&console.log(svgRoot.getElementById(cell.getTopWall()),cell.getTopWall());
                }
            break;
            }
        }
    }
}
/*根据编号获知是哪一面墙*/
Maze.prototype.getWhichWall = function(mcellno, rcellno){
    var mark = 0;
    if(mcellno == rcellno - 1) //主墙是右墙
        mark = 1;
    else if(mcellno == rcellno + 1) //主墙是右墙
        mark = -1;
    else if(mcellno < rcellno)    //主墙是下墙
        mark = 2;
    else mark = -2;
    return mark;
}

Maze.prototype.init = function(evt)
{
    svgdoc = evt.target.ownerDocument;
    svgRoot = svgdoc.getElementById('mazesvg');
    var group = svgRoot.getElementById('group');
    for(var i = 0; i <= this.initParams.rows; ++i){
        for(var j = 0; j < this.initParams.columns; ++j){
            var vline = svgdoc.createElementNS('http://www.w3.org/2000/svg','line');
            vline.setAttribute('id', 'v_'+i+'_'+j);
            vline.setAttribute('x1', this.initParams.cellsize * (i)+40);
            vline.setAttribute('y1', this.initParams.cellsize * (j)+40);
            vline.setAttribute('x2', this.initParams.cellsize * (i)+40);
            vline.setAttribute('y2', this.initParams.cellsize * (j+1)+40);
            vline.setAttribute('style', 'stroke:SlateGray');
            group.appendChild(vline);
            vline.addEventListener('mouseover', function(){console.log(this.getAttribute('id'))});
        }
    }
    for(i = 0; i < this.initParams.columns; ++i){
        for(j = 0; j <= this.initParams.rows; ++j){
            var hline = svgdoc.createElementNS('http://www.w3.org/2000/svg','line');
            hline.setAttribute('id', 'h_'+i+'_'+j);
            hline.setAttribute('x1', this.initParams.cellsize * (i)+40);
            hline.setAttribute('y1', this.initParams.cellsize * (j)+40);
            hline.setAttribute('x2', this.initParams.cellsize * (i+1)+40);
            hline.setAttribute('y2', this.initParams.cellsize * (j)+40);
            hline.setAttribute('style', 'stroke:SlateGray');
            group.appendChild(hline);
            hline.addEventListener('mouseover', function(){console.log(this.getAttribute('id'))});
        }
    }
    var exitPort = svgRoot.getElementById('h_'+(this.initParams.rows-1)+'_'+this.initParams.columns);
    var enterPort = svgRoot.getElementById('v_0_0');
    enterPort.setAttribute('style', 'stroke:Wheat');
    exitPort.setAttribute('style', 'stroke:Wheat');
    //初始化迷宫
    this.randomMaze();
}

//随机获取整数
function GetRandomNum(Min, Max){
    var Range = Max - Min;
    var Rand = Math.random();
    return(Min + Math.floor(Rand * Range));
}

var maze = new Maze();

接下来是Cell.js的代码:

//定义房间对象
function Cell(rowidx, colidx, rows, cols){
    //横坐标
    this.x = rowidx;
    //纵坐标
    this.y = colidx;
    //横坐标阈值
    this.xMax = rows - 1;
    //纵坐标阈值
    this.yMax = cols - 1;
    //房间编号(按行)
    this.cellno = cols * colidx + rowidx;
    //房间墙体的标识
    this.top = 1;
    this.right = 1;
    this.bottom = 1;
    this.left = 1;
}

Cell.prototype.getCellnoByCord = function(x, y){
    return (this.yMax + 1) * y + x;
}

//获取邻居(返回邻居的编号)
Cell.prototype.getNeighbors = function(){
    var neighbors = [], x = this.x, y = this.y, xm = this.xMax, ym = this.yMax;
    if(x > 0){  //有左邻居
        neighbors.push(this.getCellnoByCord(x-1, y));
    }
    if(y > 0){  //有上邻居
        neighbors.push(this.getCellnoByCord(x, y-1));
    }
    if(x < xm){  //有右邻居
        neighbors.push(this.getCellnoByCord(x+1, y));
    }
    if(y < ym){  //有下邻居
        neighbors.push(this.getCellnoByCord(x, y+1));
    }
    return neighbors;
}

Cell.prototype.getLeftWall = function(){
    var wallid = 'v_'+this.x+'_'+this.y;
    return wallid;
}

Cell.prototype.getRightWall = function(){
    var wallid = 'v_'+(this.x+1)+'_'+this.y;
    return wallid;
}

Cell.prototype.getBottomWall = function(){
    var wallid = 'h_'+this.x+'_'+(this.y+1);
    return wallid;
}

Cell.prototype.getTopWall = function(){
    var wallid = 'h_'+this.x+'_'+this.y;
    return wallid;
}

算法模型代码:

这里的算法采用了两种启发式策略,即“按秩求并”和“路径压缩”:

/*****************
 * 并查集算法绘制迷宫
 * 运用2种启发式策略
*/
var DSets = function(maxSize)
{
    //最大节点数
    this.maxSize = maxSize;
    //父节点集合
    this.pa = new Array(maxSize);
    //高度上界
    this.rank = new Array(maxSize);
}

/*创建单元集*/
DSets.prototype.makeSet = function(i){
    this.pa[i] = i;
    this.rank[i] = 0;
}

/*带路径压缩的查找*/
DSets.prototype.findSet = function(i){
    if(i != this.pa[i])
        this.pa[i] = this.findSet(this.pa[i]);
    return this.pa[i];
}

/*按秩合并*/
DSets.prototype.unionSet = function(i, j){
    i = this.findSet(i);
    j = this.findSet(j);
    if(this.rank[i] > this.rank[j])
        this.pa[j] = i;
    else{
        this.pa[i] = j;
        if(this.rank[i] == this.rank[j])
            this.rank[j]++;
    }
}

 

posted @ 2015-09-15 12:55  微观link  阅读(467)  评论(0)    收藏  举报