这是我的一个不相交集的Objective-C实现。 - 正数指向父母。 - 负数表示根&孩子数。 (所以他们每个人都开始脱节-1。) - 索引充当我正在分组的数据。 似乎工作正常...只是有几个问题。
查找:如何压缩路径?因为我没有以递归方式进行,所以我是否必须存储路径并在找到root后再次循环设置?
加入:我正在加入儿童计数而不是深度!?我猜这不对。如果深度相等,我是否需要在加入期间做一些特别的事情?
感谢。
DisjointSet.h
@interface DisjointSet : NSObject
{
NSMutableArray *_array;
}
- (id)initWithSize:(NSInteger)size;
- (NSInteger)find:(NSInteger)item;
- (void)join:(NSInteger)root1 root2:(NSInteger)root2;
@end
DisjointSet.m
#import "DisjointSet.h"
@implementation DisjointSet
- (id)initWithSize:(NSInteger)size
{
self = [super init];
if (self)
{
_array = [NSMutableArray arrayWithCapacity:size];
for (NSInteger i = 0; i < size; i++)
{
[_array addObject:[NSNumber numberWithInteger:-1]];
}
}
return self;
}
- (NSInteger)find:(NSInteger)item
{
while ([[_array objectAtIndex:item] integerValue] >= 0)
{
item = [[_array objectAtIndex:item] integerValue];
}
return item;
}
- (void)join:(NSInteger)root1 root2:(NSInteger)root2
{
if (root1 == root2) return;
NSInteger data1 = [[_array objectAtIndex:root1] integerValue];
NSInteger data2 = [[_array objectAtIndex:root2] integerValue];
if (data2 < data1)
{
[_array setObject:[NSNumber numberWithInteger:data2 + data1] atIndexedSubscript:root2];
[_array setObject:[NSNumber numberWithInteger:root2] atIndexedSubscript:root1];
}
else
{
[_array setObject:[NSNumber numberWithInteger:data1 + data2] atIndexedSubscript:root1];
[_array setObject:[NSNumber numberWithInteger:root1] atIndexedSubscript:root2];
}
}
@end
答案 0 :(得分:4)
对于find操作,不需要存储路径(与_array
分开)或使用递归。这些方法中的任何一种都需要O(P)存储(P =路径长度)。相反,您可以只遍历路径两次。第一次,你找到了根。第二次,您将所有子项设置为指向根。这需要O(P)时间和O(1)存储。
- (NSInteger)findItem:(NSInteger)item {
NSInteger root;
NSNumber *rootObject = nil;
for (NSInteger i = item; !rootObject; ) {
NSInteger parent = [_array[i] integerValue];
if (parent < 0) {
root = i;
rootObject = @(i);
}
i = parent;
}
for (NSInteger i = item; i != root; ) {
NSInteger parent = [_array[i] integerValue];
_array[i] = rootObject;
i = parent;
}
return root;
}
对于合并操作,您希望存储每个根的等级(这是其深度的上限),而不是每个根的后代数。存储每个根的等级允许您将较短的树合并到较高的树中,这保证了查找操作的O(log N)时间。当要合并的树具有相同的等级时,等级才会增加。
- (void)joinItem:(NSInteger)a item:(NSInteger)b {
NSInteger aRank = -[_array[a] integerValue];
NSInteger bRank = -[_array[b] integerValue];
if (aRank < bRank) {
NSInteger t = a;
a = b;
b = t;
} else if (aRank == bRank) {
_array[a] = @(-aRank - 1);
}
_array[b] = @(a);
}
答案 1 :(得分:1)
您肯定应该使用递归实现路径压缩。我甚至不会考虑尝试非递归地进行。
实现分离集数据结构应该非常简单,并且可以在几行中完成。它非常非常容易地从伪代码转换为任何编程语言。您可以在Wikipedia上找到伪代码。 (不幸的是,我无法阅读Objective-C,所以我无法判断你的代码是否正确)。
答案 2 :(得分:1)
是。要在没有递归的情况下实现最高的祖先压缩,您需要维护自己的列表。在链中进行一次传递以获取指向需要更改父指针的集合的指针,并学习根。然后进行第二次传递以更新必要的父指针。
递归方法正在做同样的事情。第一遍是递归的“清盘”,它将需要父指针更新的集存储在程序堆栈上。随着递归的展开,第二遍反过来。
我与那些说递归方法总是最好的人不同。在合理数量的系统(尤其是嵌入式系统)中,程序堆栈的大小有限。有些情况下,在查找之前连续执行许多工会。在这种情况下,对于n个元素,父链的大小可以是O(n)。这里通过递归折叠可以吹掉堆栈。由于您在Objective C中工作,这可能是iOS。我不知道那里的默认堆栈大小,但是如果你使用递归,那么值得一看。它可能比你想象的要小。 This article表示辅助线程为512K,主线程为1Mb。
迭代,恒定空间替代
实际上,我写作的主要原因是要指出你仍然得到O(log ^ * n)进行n次固定操作 - 只比阴影效率低,而且仍然有效O(1) - 如果你只进行二次因子压缩:在查找操作中,更改父指针,使它们指向祖父母而不是根。这可以通过在恒定存储中的迭代来完成。 This lecture at Princeton讨论了这个算法并在一个循环中用5行C实现它。见幻灯片29。