Loops 递归联合查找可以优化吗?

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 =

在实现union find时,我通常会编写带有路径压缩的
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的算法方面。可以证明,递归永远不会很深,因此第一个版本实现起来是完全安全的。但是我不知道有任何这样的证明,所以我想现在,我会试着记住编码迭代版本,其中的递归可以是线性深度的。想象一下,总是将其中一个列表的头链接到一个单例列表。这将构建一个线性长度的链。现在在最深的节点上进行查找。很好。有了“等级”当然不会发生这种情况,但没有它我们必须小心。