簡體   English   中英

從選擇排序中消除尾遞歸

[英]Eliminate tail recursion from selection sort

代碼:(我嘗試翻譯成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);
  }
}

作業問題要求消除該代碼中的尾部遞歸。 這是否意味着用迭代循環替換上一個遞歸調用? 還是添加額外的遞歸調用?

我認為問題可能源於我不知道常規遞歸算法和尾遞歸算法之間的區別。 如果我做錯了,請糾正我,但是我的理解是,尾部遞歸用於減少遞歸調用的次數,從而提高效率,除非該調用將是代碼中的最后一條指令。 關鍵是遞歸調用較少意味着要在內存中存儲的遞歸堆棧較少。

編輯:在固定代碼中的交換調用中出錯。

基本上,尾部遞歸是函數調用自身時具有的功能,但是此后不需要執行任何操作,除了可能返回遞歸調用的結果。 這是遞歸的一種特殊情況……不是遞歸的工作原理,而是實際上很容易變成循環。 對於沒有優化尾遞歸的編譯器/解釋器,這可能意味着正確工作和溢出調用堆棧之間的區別。

例如:

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

(不過,額外的好處是:您可以使用“連續傳遞樣式”技術將其轉換為尾遞歸函數。基本上,在這種情況下,您將結果與其他參數一起傳遞。)

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;
    }
}

您可以在此處查看有關尾遞歸的知識什么是尾遞歸? 什么是尾遞歸消除? 您可能會嘗試此代碼,但我尚未測試,只是這個想法

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 ++;
  }
}

因為您可以在尾遞歸版本中看到它,它只是更新 ,所以我們可以嘗試while循環

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM