一、简介
不相交集:不相交集类维持着多个彼此之间没有交集的子集的集合,可以用于 判断两个元素是否属于同一个集合,或者合并两个不相交的子集。比如, { {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;
}
输出结果: