讓我重新看看漢諾塔問題

漢諾塔可以說是一個非常經典的遞歸問題了,在很多書上也會把它作爲遞歸的入門題,用來介紹遞歸的基本概念。

故事的背景和問題的具體內容就不在這裏介紹了,我覺得我並沒有搞明白遞歸是怎麼一回事,比起迭代,遞歸從頭到腳都透露着一種神祕;雖然我想不到,但是遞歸的邏輯很清晰。這兩者並不矛盾。

抒情完畢,說正事。如何解決漢諾塔問題(在這裏相當於是把A移到C,並且直接在C上修改)?很簡單:

function hanota(A: number[], B: number[], C: number[]): void {
  C.push(...A);
}

開玩笑的。正經的遞歸解法大概是這樣:

function hanota(A: number[], B: number[], C: number[]): void {
  function move(n: number, A: number[], B: number[], C: number[]) {
    if (n == 1) C.push(A.pop()!);
    else {
      move(n - 1, A, C, B);
      C.push(A.pop()!);
      move(n - 1, B, A, C);
    }
  }
  move(A.length, A, B, C);
}

怎麼理解這個問題?先把上面n - 1個較小的塊當成一個整體,從A先搬到B暫存,然後把A最底下那個大塊搬到目標點C,然後再把暫存區B裏的剩下的塊全搬到目標點C。就是這樣。

邏輯是那個邏輯,但生不生動,又是另一碼事了。看來看去,覺得還是所謂“冰箱裏的大象”的解釋最好,在此分享一下(轉載自如何理解漢諾塔的遞歸? - IT邊界的回答 - 知乎) 。怎麼把大象放進冰箱?

  1. 把冰箱門打開;img

  2. 把大象裝進來; img

  3. 把冰箱門關上。img

其實,解決漢諾塔問題,我們只是在不斷地開門關門。我所希望的,不過是按照順序,每次把最底下的那個放到C而已。

這是最常規的遞歸解法。如果用棧呢?雖然說遞歸棧也是“棧”,但和真正的stack還是不太一樣的。當然,理論上所有能用遞歸解決的問題都可以用棧來解決。我在這裏給出一種可能的實現:

function hanota(A: number[], B: number[], C: number[]): void {
  const stack: any[] = [[A.length, A, B, C]];
  while (stack.length) {
    const top = stack.pop()!,
      n = top[0];
    if (n === 1) top[3].push(top[1].pop()!);        // C.push(A.pop()!)
    else {
      stack.push([n - 1, top[2], top[1], top[3]]);  // [n-1, B, A, C]
      stack.push([1, top[1], top[2], top[3]]);      // [  1, A, B, C]
      stack.push([n - 1, top[1], top[3], top[2]]);  // [n-1, A, C, B]
    }
  }
}

立刻就跟剛纔的遞歸一樣了。當然,因爲這裏用的是真正的棧,所以在進行“遞歸”的時候要倒序入棧,才能保證有序進行。

之前還寫了一個可讀性稍好的版本,也拿出來以供參考:

interface StackItem {
  n: number;
  src: number[];
  cache: number[];
  target: number[];
}

function hanota(A: number[], B: number[], C: number[]): void {
  const stack: StackItem[] = [{ n: A.length, src: A, cache: B, target: C }];
  while (stack.length) {
    const top = stack.pop()!;
    if (top.n === 1) top.target.push(top.src.pop()!);
    else {
      stack.push({
        n: top.n - 1,
        src: top.cache,
        cache: top.src,
        target: top.target,
      });
      stack.push({ n: 1, src: top.src, cache: top.cache, target: top.target });
      stack.push({
        n: top.n - 1,
        src: top.src,
        cache: top.target,
        target: top.cache,
      });
    }
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章