Loops 递归联合查找可以优化吗?
在实现union find时,我通常会编写带有路径压缩的Loops 递归联合查找可以优化吗?,loops,recursion,stack-overflow,tail-recursion,union-find,Loops,Recursion,Stack Overflow,Tail Recursion,Union Find,在实现union find时,我通常会编写带有路径压缩的find函数,如下所示: def find(x): if x != par[x]: par[x] = find(par[x]) return par[x] 这很容易记住,也很容易阅读。这也是许多书籍和网站对算法的描述 然而,如果进行简单的编译,这将在输入大小中使用堆栈内存线性。在许多语言和系统中,默认情况下会导致堆栈溢出 我所知道的编写find的唯一非递归方法是: def find(x): p =
find
函数,如下所示:
def find(x):
if x != par[x]:
par[x] = find(par[x])
return par[x]
这很容易记住,也很容易阅读。这也是许多书籍和网站对算法的描述
然而,如果进行简单的编译,这将在输入大小中使用堆栈内存线性。在许多语言和系统中,默认情况下会导致堆栈溢出
我所知道的编写find
的唯一非递归方法是:
def find(x):
p = par[x]
while p != par[p]:
p = par[p]
while x != p:
x, par[x] = par[x], p
return p
许多编译器似乎不太可能发现这一点。(也许哈斯克尔会这么做?)
我的问题是在什么情况下使用以前版本的
find
?如果没有一种广泛使用的语言可以消除递归,我们不应该告诉人们使用迭代版本吗?有没有更简单的迭代实现?这里似乎有两个独立的问题
首先,优化编译器能注意到这一点并重写它吗?如果不测试所有编译器和所有版本,就很难回答这个问题。我在以下代码中使用gcc 4.8.4进行了尝试:
size_t find(size_t uf[], size_t index) {
if (index != uf[index]) {
uf[index] = find(uf, uf[index]);
}
return uf[index];
}
void link(size_t uf[], size_t i, size_t j) {
uf[find(uf, i)] = uf[find(uf, j)];
}
这不实现按秩联合优化,但支持路径压缩。我使用优化级别-O3编译了此文件,组装如下所示:
find:
.LFB23:
.cfi_startproc
pushq %r14
.cfi_def_cfa_offset 16
.cfi_offset 14, -16
pushq %r13
.cfi_def_cfa_offset 24
.cfi_offset 13, -24
pushq %r12
.cfi_def_cfa_offset 32
.cfi_offset 12, -32
pushq %rbp
.cfi_def_cfa_offset 40
.cfi_offset 6, -40
pushq %rbx
.cfi_def_cfa_offset 48
.cfi_offset 3, -48
leaq (%rdi,%rsi,8), %rbx
movq (%rbx), %rax
cmpq %rsi, %rax
je .L2
leaq (%rdi,%rax,8), %rbp
movq 0(%rbp), %rdx
cmpq %rdx, %rax
je .L3
leaq (%rdi,%rdx,8), %r12
movq %rdx, %rax
movq (%r12), %rcx
cmpq %rcx, %rdx
je .L4
leaq (%rdi,%rcx,8), %r13
movq %rcx, %rax
movq 0(%r13), %rdx
cmpq %rdx, %rcx
je .L5
leaq (%rdi,%rdx,8), %r14
movq %rdx, %rax
movq (%r14), %rsi
cmpq %rsi, %rdx
je .L6
call find // <--- Recursion!
movq %rax, (%r14)
.L6:
movq %rax, 0(%r13)
.L5:
movq %rax, (%r12)
.L4:
movq %rax, 0(%rbp)
.L3:
movq %rax, (%rbx)
.L2:
popq %rbx
.cfi_def_cfa_offset 40
popq %rbp
.cfi_def_cfa_offset 32
popq %r12
.cfi_def_cfa_offset 24
popq %r13
.cfi_def_cfa_offset 16
popq %r14
.cfi_def_cfa_offset 8
ret
.cfi_endproc
查找:
.LFB23:
.cfi_startproc
pushq%r14
.cfi_def_cfa_偏移量16
.cfi_偏移量14,-16
pushq%r13
.cfi_def_cfa_偏移量24
.cfi_偏移量13,-24
pushq%r12
.cfi_def_cfa_偏移量32
.cfi_偏移量12,-32
pushq%rbp
.cfi_def_cfa_偏移量40
.cfi_偏移量6,-40
pushq%rbx
.cfi_def_cfa_偏移量48
.cfi_偏移量3,-48
leaq(%rdi,%rsi,8),%rbx
movq(%rbx),%rax
cmpq%rsi,%rax
乙脑L2
leaq(%rdi,%rax,8),%rbp
movq 0(%rbp),%rdx
cmpq%rdx,%rax
je.L3
leaq(%rdi,%rdx,8),%r12
movq%rdx,%rax
movq(%r12),%rcx
cmpq%rcx,%rdx
乙脑L4
leaq(%rdi,%rcx,8),%r13
movq%rcx,%rax
movq 0(%r13),%rdx
cmpq%rdx,%rcx
je.L5
leaq(%rdi,%rdx,8),%r14
movq%rdx,%rax
movq(%r14),%rsi
cmpq%rsi,%rdx
乙脑L6
致电find//感谢您提供详细的答案。我的第二个问题更多的是关于union find的算法方面。可以证明,递归永远不会很深,因此第一个版本实现起来是完全安全的。但是我不知道有任何这样的证明,所以我想现在,我会试着记住编码迭代版本,其中的递归可以是线性深度的。想象一下,总是将其中一个列表的头链接到一个单例列表。这将构建一个线性长度的链。现在在最深的节点上进行查找。很好。有了“等级”当然不会发生这种情况,但没有它我们必须小心。