快速联盟的时间复杂性是什么?

时间:2017-03-27 00:33:22

标签: algorithm quick-union

我参加了数据结构和算法课程。作者提到Quick Find是O(N ^ 2),这是有意义的(假设N个对象上的N个联合操作可能需要N * N个数组访问)。但是,我不明白为什么Quick Union会更好。似乎在最坏的情况下,一个长的窄树,对N个物体的N Find操作也会导致O(N ^ 2),但是材料说它是O(N)。

所以,一个是二次时间,一个是线性的。我不确定我理解为什么会有区别。例如:

快速查找方法

int[] id = new int[10];

for(int i = 0; i < 10; i++)
    id[i] = i;  

// Quick find approach

int QuickFind(int p)
{
    return id[p];
}

public void Union(int p, int q)
{
    int pId = find(p);
    int qId = find(q);

    if (pId == qId)
        return;

    for (int i = 0; i < id.length; i++)
    {
        if(id[i] == pId)
            id[i] = qId;
    }
}

快速联合方法

int Find(int p)
{
    while(p != id[p])
        p = id[p];

    return p;
}

void QuickUnion(int p, int q)
{
    int pRoot = Find(p);
    int qRoot = Find(q);

    if(pRoot == qRoot)
        return;

    id[pRoot] = qRoot;
}

2 个答案:

答案 0 :(得分:1)

// Naive implementation of find
int find(int parent[], int i)
{
    if (parent[i] == -1)
        return i;
    return find(parent, parent[i]);
}

// Naive implementation of union()
void Union(int parent[], int x, int y)
{
    int xset = find(parent, x);
    int yset = find(parent, y);
    parent[xset] = yset;
}

以上union()find()是天真的,最坏的情况时间复杂度是线性的。为表示子集而创建的树可以是倾斜的,并且可以变得像链接列表。以下是最坏情况的示例。

Let there be 4 elements 0, 1, 2, 3

Initially all elements are single element subsets.
0 1 2 3 

Do Union(0, 1)
   1   2   3  
  /
 0

Do Union(1, 2)
     2   3   
    /
   1
 /
0

Do Union(2, 3)
         3    
        /
      2
     /
   1
 /
0

在最坏的情况下,上述操作可以优化为O(Log n)。我们的想法是始终在较深的树的根部附加较小的深度树。此技术通过排名 称为 union。术语等级是首选而不是高度,因为如果使用路径压缩技术(我在下面讨论过它),那么等级并不总是等于高度。

Let us see the above example with union by rank
Initially all elements are single element subsets.
0 1 2 3 

Do Union(0, 1)
   1   2   3  
  /
 0

Do Union(1, 2)
   1    3
 /  \
0    2

Do Union(2, 3)
    1    
 /  |  \
0   2   3

天真方法的第二个优化是 路径压缩 。想法是在调用find()时展平树。为元素find()调用x时,将返回树的根。 find()操作从x遍历以查找根。路径压缩的想法是将找到的根作为x的父级,这样我们就不必再遍历所有中间节点。如果x是子树的根,那么x下所有节点的路径(到根)也会压缩。

Let the subset {0, 1, .. 9} be represented as below and find() is called
for element 3.
              9
         /    |    \  
        4     5      6
     /     \        /  \
    0        3     7    8
            /  \
           1    2  

When find() is called for 3, we traverse up and find 9 as representative
of this subset. With path compression, we also make 3 as child of 9 so 
that when find() is called next time for 1, 2 or 3, the path to root is 
reduced.

               9
         /    /  \    \
        4    5    6     3 
     /           /  \   /  \
    0           7    8  1   2           

这两种技术相辅相成。每个操作的时间复杂度甚至小于O(logn)O(n)。事实上,摊销的时间复杂度实际上变得很小。

我没有用上面的优化发布代码,因为它是我猜的赋值部分。希望它有所帮助!

答案 1 :(得分:1)

我也碰到了这一点。没错,对N个对象的N个find操作也会导致Quick-union的O(N ^ 2)。但是,主要区别在于,使用快速查找,在执行union操作时,您将始终必须遍历所有对象,这在最坏的情况和最佳的情况下都是如此。

使用Quick-union不需要遍历所有对象,而可以在恒定时间内完成两个对象的合并。

这两种方法的最坏情况均为O(N ^ 2)。在某些情况下,根据输入的性质,快速联合可能会更快一些。

这是因为使用快速查找,联合操作将始终具有大于或等于N的计算复杂度。对于快速联合而言,情况并非如此,find操作可以执行小于N的计算