您的位置:首页 > 其它

连通问题-快速合并法

2010-08-27 14:47 204 查看
快速合并法


思想
:节点列表中每个节点初始存放的内容为本节点。节点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]];

通过这样压缩的树,在节点数较多的情况下,基本是一颗扁平的树,查找连通性效率很高。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: