连通问题-快速合并法

来源:互联网 发布:淘宝上的药品能买吗 编辑:程序博客网 时间:2024/04/25 15:36

快速合并法
思想:节点列表中每个节点初始存放的内容为本节点。节点node1和节点node2连通,那么将node1作为node2的子节点,node2作为node1的父节点,那么node1中存放的内容即为node2。所以在快速合并法中,节点node中存放的内容实际上是节点node的父节点。明白这点,针对一个连通对时,将其中一个节点作为父节点father,另一个作为子节点son,将子节点son的内容存放上father,这样做似乎是正确没有问题,但是仔细想想,son节点中存放的内容是父节点,如果有两个连通对,都是针对一个son的,那么他将有两个父节点,一个存储单元如何存放两个父节点呢?这个问题其实也是可以解决,仔细想想,如果节点node1和node2连通,那么node1中的父节点和node2的父节点肯定也是连通的,递推上去,node1的父节点的父节点....和node2的父节点的父节点....肯定也是连通的,那么在node1的父节点的父节点.....上,最头上的一个节点baseNode(即根节点),它存放的内容应该是自己(它没有父节点),这样把node1的根节点和node2的根节点连接起来(可以让node2根节点做node1根节点的子节点,也可以让node1根节点做node2根节点的子节点,这里将会进一步优化算法的地方),node1和node2就连通的了,问题就解决了。按照此规则,最终所有连通的节点都将会有一个共同的根节点(baseNode)。

伪代码
//节点中的内容----这里假设节点个数为10的情况
int[] nodesContext = {0,1,2,3,4,5,6,7,8,9};
//连通节点对:p、q
//找到节点p的根节点i
for( i = p; i != nodesContext[i]; i = nodesContext[i]);
//找到节点q的根节点j
for( j = q; j != nodesContext[j]; j = nodesContext[j]);

//如果p和q的根节点是相同的,不必执行后续步骤,对新连通节点对重复上述步骤
if( i == j ) continue;

//节点i成为节点j的子节点
nodesContext[i] = j;

代码执行过程数据变化情况:
                               0,1,2,3,4,5,6,7,8,9                0,1,2,3,4,5,6,7,8,9   
connectNodes[0]    nodesContext(BEFORE)        nodesContext(AFTER)        p    q    i     j
1,2                          0,1,2,3,4,5,6,7,8,9                0,2,2,3,4,5,6,7,8,9             1    2    1    2
2,4                          0,2,2,3,4,5,6,7,8,9                0,2,4,3,4,5,6,7,8,9             2    4    2    4
0,8                          0,2,4,3,4,5,6,7,8,9                8,2,4,3,4,5,6,7,8,9             0    8    0    8
6,7                          8,2,4,3,4,5,6,7,8,9                8,2,4,3,4,5,7,7,8,9             6    7    6    7
7,3                          8,2,4,3,4,5,7,7,8,9                8,2,4,3,4,5,7,3,8,9             7    3    7    3
3,5                          8,2,4,3,4,5,7,3,8,9                8,2,4,5,4,5,7,3,8,9             3    5    3    5
9,1                          8,2,4,5,4,5,7,3,8,9                8,2,4,5,4,5,7,3,8,4             9    1    9    4
5,9                          8,2,4,5,4,5,7,3,8,4                8,2,4,5,4,4,7,3,8,4             5    9    5    4
9,8                          8,2,4,5,4,4,7,3,8,4                8,2,4,5,8,4,7,3,8,4             9    8    4    8

算法的分析
    快速合并法比快速查找法好在它无需遍历节点列表,但也相应增加了追踪两节点是否连通的复杂,因为你不递推到节点node1的根节点,和node2的根节点,你是不知道它俩是否连通。这就带来负面影响,当你最终生成的树的深度较深,递推消耗的时间就会很大,这样合并的效率是否一定会比遍历节点列表快就不好说了,毕竟查找法,是可以直接知道两节点是否连通,只需看看两节点中存放的根节点是否相同,而合并法节点中存放的是父节点。

算法优化
    接下来考虑一下如何改进快速合并法,如果要提高后续查找两节点连通性的效率,那么最好就是让最终生成的树不要太深。想到合并的过程中-“可以让node2根节点做node1根节点的子节点,也可以让node1根节点做node2根节点的子节点”,这个过程其实是可以优化,来减少生成树的深度的。当你的node1的根节点生成的树tree1,node2的根节点生成的树tree2,tree1的深度假设为5,tree2的深度为3,这时你是将tree1的根节点连到tree2根节点上,当个子节点呢?还是将tree2的根节点连接到tree1根节点上,当子节点呢?很明显,如果你将tree1的根节点连到tree2上,那么生成的树treeUnion深度为6,而将tree2的根节点连到tree1上,生成的树深度为5。所以采用一种规则:始终将深度小的树接到深度大的树上,这样能保证最后合成的树的深度是最小的。
    怎样做到将深度小的树连接到深度大的树上呢?这就需要对每棵树的深度做记录,在每颗树的根节点记录当前树的深度。
伪代码改动:
//初始所有节点的深度为1
for(i = 0; i < nodeCount; i++)//nodeCount为节点数
      deep[i] = 1;


//找到节点p的根节点i
for( i = p; i != nodesContext[i]; i = nodesContext[i]);
//找到节点q的根节点j
for( j = q; j != nodesContext[j]; j = nodesContext[j]);

if(deep[i] < deep[j])
{ nodesContext[i] = j; deep[j] = max(deep[i]+1,deep[j]); }
else
{ nodesContext[j] = i; deep[i] = max(deep[j]+1,deep[i]); }


      通过上面的优化,树的深度有很大改善,这样递推到根所花费的时间会大大减少。到此,似乎已经ok了,但是一些书籍中还提供了更好的方法,能够进一步对树的深度进行压缩,争取做到每个节点尽可能的连接到根节点上,这样递推的时间是最短的,效率也会有很大的提升。这种方法就叫做路径压缩法,路径压缩法有很多,拿个比较好理解的等分路径压缩法来对上述代码再做一次改动。
伪代码改动:
//找到节点p的根节点i
for( i = p; i != nodesContext[i]; i = nodesContext[i])
    nodesContext[i] = nodesContext[nodesContext[i]];
//找到节点q的根节点j
for( j = q; j != nodesContext[j]; j = nodesContext[j]);
    nodesContext[j] = nodesContext[nodesContext[j]];

     通过这样压缩的树,在节点数较多的情况下,基本是一颗扁平的树,查找连通性效率很高。

原创粉丝点击