不相交集类以及生成迷宫

一、简介

不相交集:不相交集类维持着多个彼此之间没有交集的子集的集合,可以用于 判断两个元素是否属于同一个集合,或者合并两个不相交的子集。比如, { {1,3,5},{2},{4},{6,7} }这整体就是一个不相交集合。里面的一些子集也是彼此互不相交的。

基本数据结构:使用一棵树来表示一个子集,树的根节点可以代表当前子集,而所有子集的集合就是一个森林。而对于根树的存储结构,采用数组实现。s[i]表示元素 i的父节点。根节点令 s[i]=-1。

二、不相交集类操作

1.合并( union ):为了执行两个集合的union运算,我们通过使一颗树的根节点的父链链接到另一棵树的根节点。显然操作所花费常数时间

 

代码:

    // 任意合并两个不相交集合
    void unionSets( int root1, int root2 )
    {
        s[ root2 ] = root1;
    }

2.查找( find ): 对元素x的一次find( x )操作通过返回包含 x 的树的根完成。执行该操作的时间与代表 x 的节点的深度成正比,所以一次最坏运行时间为O( N )

代码:

    // 寻找 x 所在集合
    int find( int x )   const
    {
        if( s[ x ] < 0 )
            return x;
        else
            return find( s[ x ] );
    }

3.灵巧合并算法:降低子树的深度

(1)按子树的大小合并:使得总让较小的树成为较大树的子树。可以证明任何子树的深度不会超过logN。也已证明连续M次运算需要O(M)平均时间

代码:

    // 按树大小合并
    void unionSetsBySzie( int root1, int root2 )
    {
        if( s[ root2 ] < s[ root1 ] )           // 如果 root2 比 root1大
        {
            s[ root2 ] += s[ root1 ];
            s[ root1 ] = root2;                 // 使 root2 为新的根
        }           
        else
        {
            s[ root1 ] += s[ root2 ];
            s[ root2 ] = root1;                 // 使 root1 为新根      
        }
    }

(2)按子树的深度合并:使得总让深度较浅的树成为较深树的子树。可以证明任何子树的深度不会超过logN

代码:

    // 按根深合并
    void unionSetsByHight( int root1, int root2 )
    {
        if( s[ root2 ] < s[ root1 ] )           // 如果 root2 比 root1深
            s[ root1 ] = root2;                 // 使 root2 为新的根
        else
        {
            if( s[ root1 ] == s[ root2 ] )      // 如果相同 , 则更新高度 
                --s[ root1 ];
            s[ root2 ] = root1;                 // 使 root1 为新根
        }
    }

4.路径压缩:通过find( x )查找降低find( x )递归路径上各子树的深度

设操作为find( x ),此时路径压缩的效果是,从 x 到根的路径上的每一个节点都使它的父节点变成根节点。

业已证明,在这种情况下进行路劲压缩时,连续 M 次运算最多需要O( MlogN )的时间

意:

  • 路径压缩与按大小求并完全兼容,这使得两个操作可以同时实现。
  • 路径压缩不完全与按深度求并兼容,因为路径压缩可以改变树的高度。

代码:

   // 寻找 x 所在集合 压缩路径
    int PathCompressionfind( int x )   
    {
        if( s[ x ] < 0 )
            return x;
        else
            return s[ x ] = PathCompressionfind( s[ x ] );
    }

三、迷宫

 

基本思路:生成迷宫一个简单的算法是从各处的墙壁开始。此时,我们不断地随机选择一墙面,如果该墙分割的单元彼此不连通,那么就拆掉这面墙。如果重复这个过程直到开始单元和终止单元连通,那么就得到一个迷宫。

/*
 * @Author: lsyy
 * @Date: 2020-02-28 11:37:49
 * @LastEditTime: 2020-02-29 17:25:38
 * @LastEditors: Please set LastEditors
 * @Description: 迷宫
 * @FilePath: \DisjSets\Src\main.cpp
 */

#include <iostream>  
#include <vector>  
#include "DisjSets.h"
#include "UniformRandom.h"
#define N 50
bool wallrow[ N + 1 ][ N ] = { 0 };       // 行 _
bool wallcol[ N ][ N + 1 ] = { 0 };       // 列 |  
/**
 * @description: 获得二维坐标
 * @param {type} Pos 一维坐标 PosX PosY 二维坐标
 * @return: null
 */
inline void GetPos( int Pos, int & PosX, int & PosY )
{
    PosX = Pos / N;
    PosY = Pos % N;
}
/**
 * @description: 打印迷宫
 * @param {type} null
 * @return: null
 */
void print( )
{
    for( int i = 0; i < N + 1 ; i++ )
    {
        if( i == 0 )                    // 打印第一行
        {
            std::cout << " " << " ";
            for (size_t i = 1; i < N; i++)
            {
                std::cout << " " << "_";
            }
        }
        else
        {
            for( int j = 0; j < N + 1; j++ )
            {
                if( ( ( i - 1 ) == 0 && j == 0 ) || 
                    ( ( i - 1 ) == ( N - 1 ) && j == N ) )
                    std::cout << " ";               // 出入口部分
                else
                    wallcol[ i - 1 ][ j ] ? std::cout << " " : std::cout << "|";
                if( j < N )
                    if( i == N && j == ( N - 1 ) )
                        std::cout << " ";           // 出入口部分
                    else
                        wallrow[ i ][ j ] ? std::cout << " " : std::cout << "_";
            }
        }
        std::cout << std::endl;
    }
}
int main( )
{
    UniformRandom random;
    DisjSets ds( N * N );
    
    while ( !ds.connect( 0, N * N - 1 ) )                // 终止条件
    {
        int PosX, PosY = 0;
        int element = random.nextInt( 0, N * N - 1 );
        GetPos( element, PosX, PosY );
       
        // 0为列 1为行
        if( random.nextInt( 2 ) )               // 行 _
        {         
            if( element < ( N * ( N - 1 ) ) && ! ( ds.connect( element, element + N ) ) )
            {
                ds.unionSets( element, element + N );
                wallrow[ PosX + 1 ][ PosY ] = true;
            }   
        }
        else                                    // 列 |
        {
            if( element % N != ( N - 1 ) && ( ! ds.connect( element, element + 1 ) ) )
            {
                ds.unionSets( element, element + 1 );
                wallcol[ PosX ][ PosY + 1 ] = true;
            }
        }    
    }
    print( );
    return 0;
}

输出结果:

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值