我试图解决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>")))*))+\)$
类似。
任何帮助/指导或解释的详细说明都会有很大帮助。
答案 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;在上面,考虑箭头反过来会更自然。
答案 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);
}