Union-Find:删除后继者

时间:2017-03-17 15:07:17

标签: algorithm data-structures union-find

我试图解决Union-Find的问题,就像

一样
  

删除后继者。给定一组N个整数S = {0,1,...,N-1}和a   以下形式的请求序列:

     

从S中删除x找到x的后继:S中的最小y,这样   y≥x。设计一个数据类型,以便所有操作(构造除外)   应该采用对数时间或更好。

即使我发现很少有解决方案和文章解释如何使用Union-Find来完成此操作,但我无法想象它是如何工作的。

例如: Delete(X)可以通过Union(X,X+1)来完成Successor(X),但是它如何充当删除,我只是无法想象。与查找^(?<FunctionName>\w+)\((?>(?(param),)(?<param>(?>(?>[^\(\),"]|(?<p>\()|(?<-p>\))|(?(p)[^\(\)]|(?!))|(?(g)(?:""|[^"]|(?<-g>"))|(?!))|(?<g>")))*))+\)$ 类似。

任何帮助/指导或解释的详细说明都会有很大帮助。

5 个答案:

答案 0 :(得分:6)

我们可以设置union-find数据结构来表示这个问题。不变量是root(x)将最小y存储在S中,以便y >= x

首先,我们可以确保节点1..N上的初始union-find数据结构满足不变量:我们只需确保每个初始节点i存储i

为了模拟x的移除,我们执行了union(x, x+1)。我们必须确保我们的union-find实现保留了我们的不变量。如果我们将root(x)加入root(x+1),那很好,但如果我们加入root(x+1)root(x),那么我们需要将root(x+1)的值存储到节点root(x)

我们需要小心谨慎,以确保union在保证的O(log n)时间内运行。为此,我们需要为每个节点存储以节点为根的树的大小。这是一个实现和一个简单的例子。

class Node:
    def __init__(self, i):
        self.i = i
        self.size = 1
        self.max = i
        self.root = self

def root(node):
    r = node
    while r.root != r:
        r = r.root
    # perform path compression
    while node.root != node:
        node, node.root = node.root, r
    return r

def union(n1, n2):
    n1 = root(n1)
    n2 = root(n2)
    if n1.size < n2.size:
        n1, n2 = n2, n1
    n2.root = n1
    n1.size += n2.size
    n1.max = max(n1.max, n2.max)

def Sfind(uf, i):
    return root(uf[i]).max

def Sdelete(uf, i):
    union(uf[i], uf[i+1])

N = 100
S = dict((i, Node(i)) for i in xrange(1, N))
Sdelete(S, 10)
Sdelete(S, 12)
Sdelete(S, 11)

for i in [10, 12, 13, 20]:
    print i, Sfind(S, i)

这是一个例子。我们从5个节点开始,逐步进行联合(2,3),联合(4,5)和联合(3,4) - 这对应于删除2,然后是4,然后是3.注意图中的箭头从a到b对应a.root = b。当我谈到根植于节点的树#34;在上面,考虑箭头反过来会更自然。

没有节点被删除。

no nodes deleted

2删除 - 联合(2,3)

2 deleted

2和4删除 - 联合(2,3),联合(4,5)

2 and 4 deleted

2,3,4删除 - 联合(2,3),联合(4,5),联合(3,4)

2, 3, 4 deleted

答案 1 :(得分:3)

首先,假设列表中有10个数字,从0到9。

0 1 2 3 4 5 6 7 8 9

就像在常规加权联合查找中一样,这些数字中的每一个都是数组索引,并且数组索引值的内容表示数组索引的父级。

因此,最初,父级0为0,而根数0(祖父母中的最祖父母)也为0。所有数字都是如此。

现在,我们删除一个数字,例如5。

删除5表示,我们实际上是在说联合(5,6)。

所以,这正在发生。

visualize what happens when 5 is removed

在当前阶段,如果我们想找到数字x的后继者,我们只需将其作为根(x + 1)即可找到。因此,4的后继是6,因为根(4 + 1)是6。

现在,假设我们删除了6,这意味着并集(6,7)。

这很棘手,因为在加权联合查找中,由于6-5分量的权重更大,因此应将7(7)的根加到6(6)的根。但是,如果这样做,我们将如何找到继任者?因为这会发生:

visualize what might happen if we try to remove 5 and 6 without any edit in union()

因此,如果要4的后继,我们不能说它是根(4 + 1),因为根(5)是6,但是6已被删除。 4的后继应该是7。

因此,我们可以使用另一个数组,例如ActualList。。该数组将存储需要在列表中的实际数字-对应于任何已删除数字的根。为此,需要对union()进行一行修改。

在这种情况下,actualList数组将存储与索引root(5)和root(6)对应的7。因此,actualList [root(4 + 1)]将得出4的后继者为7的正确答案。

要找到后继者,我们必须访问actualList [(root(x + 1)]而不是root(x + 1)。

这是我用Java实现的整个过程:

public class SuccessorWithDelete {

    private int id[];
    private int sz[];
    private int actualList[];
    private int N;

    public SuccessorWithDelete(int N){
        this.N = N;
        id = new int[N];
        sz = new int[N];
        actualList = new int[N];
        for(int i=0; i<N; i++){
            id[i] = i;
            sz[i] = 1;
            actualList[i] = i;
        }
    }

    // returns the root of the component the integer is in
    private int root(int i){
        while(id[i]!=i){

            i = id[i];
        }
        return i;
    }

    // weighted quick union
    public void union(Integer p, Integer q) {

        int pRoot = root(p);
        int qRoot = root(q);
        if (sz[pRoot] < sz[qRoot]) {
            id[pRoot] =  qRoot;
            sz[qRoot] = sz[qRoot] + sz[pRoot];

        } else {
            id[qRoot] = pRoot;
            sz[pRoot] = sz[pRoot] + sz[qRoot];
            actualList[pRoot] = qRoot;              // this is the crucial step
        }
    }


    public void remove(int x){
        union(x, x+1);

    }

    public int successor(int x){
        return actualList[(root(x+1))];
    }
}

答案 2 :(得分:0)

我想应该没有加权联盟。一旦你使用下一个元素进行联合(请记住,下一个元素的根将成为被删除元素的根元素),根将位于树的顶部。 如果想要将其可视化,请不要以树的形式进行可视化。相反,可视化父元素&#39;列表。

class SuccessorUF(object):
    def __init__(self, n):
        self.parents = []
        for i in range(0, n):
            self.parents.append(i)

    def root(self, p):
        while self.parents[p] != p:
            p = self.parents[p]
        return p

    def union(self, p, q):
        root_p = self.root(p)
        root_q = self.root(q)
        self.parents[root_p] = root_q

    def remove(self, p):
        """
        :param (int) p: 
        :return: 
        """
        if p == len(self.parents) - 1:
            self.parents.pop(p)
            return
        self.union(p, p + 1)

    def successor(self, p):
        if p > len(self.parents) - 1 or self.root(p) != p:
            return 'DELETED_OR_NOT_EXISTED_EVER'  # Deleted Element
        if p == len(self.parents) - 1:
            return 'LAST_ELEMENT'
        return self.root(p + 1)

答案 3 :(得分:0)

实际上,我发现可以通过在您的组件中找到最大值来解决此问题。您可以直接使用原始的加权联合查找代码,而无需更改树和根的排列。但是,这里的后继者不是根,而是组件中最大的继承者。希望对您有帮助。

答案 4 :(得分:0)

我从快速合并算法的非加权版本的路径压缩实现开始。

然后,实现这些新操作很简单:

void Remove(int x)
{
    Union(x, x + 1);
}

int SuccessorOf(int x)
{
    return RootOf(x + 1);
}

在纸上画出这些场景使我了解它是如何工作的。 对于任何有兴趣的人,这是我实现的测试用例:

const int capacity = 8;
var sut = new _03_SuccessorWithDelete(capacity);
for (int i = 0; i < capacity - 1; i++)
    sut.SuccessorOf(i).Should().Be(i + 1);

sut.Remove(3);
sut.SuccessorOf(2).Should().Be(4);

sut.Remove(2);
sut.SuccessorOf(1).Should().Be(4);

sut.Remove(4);
sut.SuccessorOf(1).Should().Be(5);

sut.Remove(6);
sut.SuccessorOf(5).Should().Be(7);

sut.Remove(5);
sut.SuccessorOf(1).Should().Be(7);
sut.SuccessorOf(0).Should().Be(1);

以及我的(最小化)实现(C#):

public sealed class _03_SuccessorWithDelete
{
    private readonly int[] id;
    public _03_SuccessorWithDelete(int n)
    {
        id = new int[n];
        for (int i = 0; i < id.Length; i++) id[i] = i;
    }
    public void Remove(int x) => Union(x, x + 1);
    public int SuccessorOf(int x) => RootOf(x + 1);
    public bool Connected(int p, int q) => RootOf(p) == RootOf(q);
    private int RootOf(int x)
    {
        while (x != id[x]) { id[x] = id[id[x]]; x = id[x]; }
        return x;
    }
    public void Union(int p, int q) => id[RootOf(p)] = RootOf(q);
}