连通问题-快速合并法
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]];
通过这样压缩的树,在节点数较多的情况下,基本是一颗扁平的树,查找连通性效率很高。
思想
:节点列表中每个节点初始存放的内容为本节点。节点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]];
通过这样压缩的树,在节点数较多的情况下,基本是一颗扁平的树,查找连通性效率很高。
相关文章推荐
- 连通问题的快速查找解法
- JAVA算法4――连通性问题之路径压缩的加权快速合并算法
- 连通区域合并问题
- 连通问题-快速查找法
- 连通区域合并问题【2】
- JAVA算法2――连通性问题之快速合并算法
- JAVA算法3――连通性问题之快速合并算法的加权版本
- 石子合并问题
- UNRECOGNIZED SELECTOR SENT TO INSTANCE 问题快速定位的方法
- 寻找必败态:博弈问题的快速解法
- 石子合并问题
- 线上服务CPU100%问题快速定位实战
- 经典石子合并问题
- C#.NET 大型企业信息化系统集成快速开发平台 4.2 版本 - 能支撑10万以上客户端的数据同步下载问题
- 线上服务CPU100%问题快速定位实战
- SQL中的行合并问题(转)
- iOS之通过PaintCode快速实现交互动画的最方便方法 未解问题
- Symfony2 合并用户提供器问题
- git使用kdiff3合并乱码问题
- sql多行转为一列的合并问题