并查集的树实现

在二叉树里面我们每个节点都有两个指针,一个指向左孩子,另一个指向右孩子,但是在并查集算法里面,一个父节点可能有很多个孩子,那我们该在每个节点设置多少个指针域呢?
答案是只放一个,并查集的查找是自下而上的,所以我们只需要在每个节点放置一个指向父节点的指针即可。这样一来是不是很像并查集的链表实现?不同的是链表是将这些节点串成了一串,而树则是众星拱月的形式。实际上链表并查集执行压缩路径算法后就是并查集的树实现(个人看法)。
下面实现一个数组形式的并查集算法:

void initialize(int numberOfElements)
{//初始化
    parent=new int[numberOfElements+1];
    for(int e=1;e<=numberOfElements;e++)
        parents[e]=0//初始化,每个元素都是一个根节点(这个程序规定,指针指向0说明根节点就是自己)
}
void unite(int rootA,int rootB)
{//合并两颗其根节点不同的树(rootA和rootB)
parent[rootB]=rootA;    
}
void find(int theElement)
{
    while(parent[theElement]!=0)//如果指针不指向0那么指向的就是上一层父节点
        theElement=parent[theElement];//循环直至找到指针指向0的子节点
    return theElement;//返回父节点为0的子节点,就是根了。
}

书上美其名曰树的并查集实现方法,实际上给的确实一个普通的数组实现方法,并且压缩路径都还没做。比如将A和B合并,A的指针指向B,然后再将A和C合并,那么find(A)找到了B,然后将B指向C那么,在和D合并,就成了C指向D,这样就成了一长串了。这样就不是普通的数组实现方式吗?
不过话说回来,链表,素组的并查集实现确实都是树型结构。

上面的代码在合并时,并没有什么规则,而是随便进行合并,而下面的代码将不同的两颗树,按照重量规则进行合并。
重量规则:若根为i的树节点数少于根为j的树的节点数,则将j作为i的父节点。否则将i作为j的父节点。

下面为基于重量合并规则的并查集算法:

struct unionFindNode
{
    int parent;//若root为真,则表示树的重量,否则是父节点的指针。
    bool root;//当且仅当是根时,值为真。
    unionFindNode()//默认构造函数,初始时,为根,则parent为重量,初始重量为1
    {
        parent=1;
        root=true;
    }
}

void initialize(int numberOfElements)
{
    node=new unionFindNode[numberOfElements+1];//因为0不放数据所以得加1
}
int find(int theElement)
{//返回元素所在树的根
    while(!node[theElement].root)//靠root判断是否为根节点与parent无关。
        theElement=node[theElement].parent;//向上移动一层
    return theElement;
}
void unit(int rootA,int rootB)
{//使用重量规则,合并其根不同的树(rootA和rootB)
if(node[rootA].parent<node[rootB].parent)
{//树rootA称为rootB的子树
node[rootB].parent+=node[rootA].parent;//重量相加赋给新根
node[rootA].root=false;//更新rootA的状态
node[rootA].parent=rootB;   
}
else
{
node[rootA].parent+=node[rootB].parent;//重量相加赋给新根
node[rootB].root=false;//更新rootA的状态
node[rootB].parent=rootA;
}   
}

基于重量规则的合并能够加快运行速度。

下面通过修改树型结构让所有的子节点直接指向根节点来再次加速查找的速度,这个方法称为路劲压缩算法。

int find(int theElement)
{//返回元素theElement所在树的根
//紧缩从元素theElement到根的路劲
int theRoot=theElement;
while(!node[theRoot].root)
    theRoot=node[theRoot].parent;
//紧缩从theElement到theRoot的路劲
int currentNode=theElement;
while(currentNode!=theRoot)//一个简单的交换数据算法
{
    int parentNode=node[currentNode].parent;
    node[currentNode].parent=theRoot;//移到第二层。
    currentNode=parentNode;//移到原来的父节点。
}
return theRoot;
}

这样一来增加了单个查找操作的时间,但是减少了以后查找操作的时间。

其他的还有路径分割和路径对折算法,这里就不多说了。

猜你喜欢

转载自blog.csdn.net/du_shuang/article/details/81259569