让我重新看看汉诺塔问题

汉诺塔可以说是一个非常经典的递归问题了,在很多书上也会把它作为递归的入门题,用来介绍递归的基本概念。

故事的背景和问题的具体内容就不在这里介绍了,我觉得我并没有搞明白递归是怎么一回事,比起迭代,递归从头到脚都透露着一种神秘;虽然我想不到,但是递归的逻辑很清晰。这两者并不矛盾。

抒情完毕,说正事。如何解决汉诺塔问题(在这里相当于是把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,
      });
    }
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章