从选择排序中消除尾递归

时间:2014-02-19 02:38:14

标签: algorithm recursion

代码:(我试图翻译成Java。它用不同的语言,我不知道):

selection_sort(int a, int b){
  if(a == b+1){
      //do nothing
  }else{
      i = minIndex(arr, a, b); //minIndex finds minimum value of 2 index's an array
      if(i != a)
          swap(arr, i, a);
      selection_sort(a+1, b);
  }
}

家庭作业问题要求消除此代码中的尾递归。这是否意味着用迭代循环替换最后一个递归调用?或者添加额外的递归调用?

我认为问题可能源于我不知道常规递归算法和尾递归算法之间的区别。如果我错了,请纠正我,但我的理解是尾递归用于通过减少递归调用的数量来提高效率,除非调用将是代码中的最后一条指令。关键是递归调用越少意味着存储在内存中的递归堆栈越少。

编辑:修复代码中的交换调用错误。

2 个答案:

答案 0 :(得分:4)

基本上,尾递归是函数调用自身时所具有的,但除此之外不需要做任何事情,除了可能返回递归调用的结果。这是一个递归的特殊情况......不是它的工作方式,而是因为很容易变成一个循环。对于不优化尾递归的编译器/解释器,这可能意味着正确工作和溢出调用堆栈之间的区别。

例如:

void countdown(int t) {
    if (t <= 1) return;
    printf("%d\n", t);
    countdown(t - 1);
}

这是尾递归的,因为在countdown调用自身之后不需要发生任何其他事情。

等对比
int factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

这是尾递归。不同之处在于函数在调用自身后需要重新获得控制权,因此它可以将结果乘以n

(虽然可以加分:你可以使用一种称为“延续传递方式”的技术将其变成尾递归函数。基本上,在这种情况下,你将结果与其他参数一起传递。)< / p>

int factorial(int n, int product = 1) {
    if (n <= 1) return product;
    return factorial(n - 1, product * n);
}

但我离题了。

无论如何,删除尾递归的编译器基本上只是调整当前的调用帧,以便变量是他们调用的函数,然后跳回到函数的开头。由于一些好的理由,你的老师可能不会那么喜欢。但是,让我们从那里开始吧。

selection_sort(int a, int b){
  tail_call:
    if(a == b+1){
      //do nothing
    }else{
      i = minIndex(arr, a, b);
      if(i != a)
          swap(arr, i, a);

      // tweak the variables so the next iteration sees (a+1, b)
      a += 1;
      goto tail_call;
    }
}

虽然我们正在重新排列东西,但空条件块相当可怕。这也可以改写为

selection_sort(int a, int b){
  tail_call:
    if(a != b+1){
      i = minIndex(arr, a, b);
      if(i != a)
          swap(arr, i, a);

      // tweak the variables so the next iteration sees (a+1, b)
      a += 1;

      goto tail_call;
    }
}

现在,你可能已经被告知goto是魔鬼。 :)(夸大其词,但不应该在有更好选择的地方使用。)所以重构要摆脱它。

我们如何摆脱它?我们找到了一个控制结构来完成goto试图做的事情。在这种情况下,while循环非常适合该法案。 condition中没有范围差异,此代码:

loop:
    if (condition) {
        ...magic...
        goto loop;
    }

完全相同

while (condition) {
    ...magic...
}

到许多编译器甚至会为两者生成完全相同的代码。

所以让我们用while循环来清理代码。

selection_sort(int a, int b){
    while (a != b+1) {
        int i = minIndex(arr, a, b);
        if(i != a)
            swap(arr, i, a);

        a += 1;
    }
}

答案 1 :(得分:1)

你可以在What is tail recursion?What is tail-recursion elimination?看到尾递归,你可以试试这段代码,我还没有测试,只是想法

selection_sort(){
  int firstIndex = 0;
  int lastIndex = arr.length;
  while(firstIndex < lastIndex) {
      int i = minIndex(arr, firstIndex, lastIndex);
      if(i != a) {
          swap(entries, i, firstIndex); 
      }
      firstIndex ++;
  }
}

因为你可以看到尾递归版本,它只是更新 a ,所以我们可以尝试while循环