在不相交的集合数据结构中实现路径压缩?

时间:2013-01-06 17:49:19

标签: objective-c algorithm data-structures disjoint-sets union-find

这是我的一个不相交集的Objective-C实现。 - 正数指向父母。 - 负数表示根&孩子数。 (所以他们每个人都开始脱节-1。) - 索引充当我正在分组的数据。 似乎工作正常...只是有几个问题。

  1. 查找:如何压缩路径?因为我没有以递归方式进行,所以我是否必须存储路径并在找到root后再次循环设置?

  2. 加入:我正在加入儿童计数而不是深度!?我猜这不对。如果深度相等,我是否需要在加入期间做一些特别的事情?

  3. 感谢。

    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
    

3 个答案:

答案 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。