【问题标题】:Towers of Hanoi solution better than O(2^n)?河内塔比 O(2^n) 更好?
【发布时间】:2012-05-21 02:18:14
【问题描述】:

对于运行时间小于 O(2n) 其中 n 是磁盘数量的 Hanoi Towers 是否有解决方案移动?我的解决方案需要 O(2n) 时间。

此外,以下解决方案是递归的。我们可以使用带有记忆概念的动态规划在更短的时间内解决这个问题吗?

public void towersOfHanoi(
        int num, 
        MyStack<Integer> from,
        MyStack<Integer> to, 
        MyStack<Integer> spare
) {
    if (num == 1) {
        int i = from.pop();
        to.push(i);
        System.out.println("Move "+i+" from "+from.getName()+" to " + to.getName());
        return;
    }
    towersOfHanoi(num - 1, from, spare, to);
    towersOfHanoi(1, from, to, spare);
    towersOfHanoi(num - 1, spare, to, from);
}

MyStack 是 Java 中 Stack 类的扩展版本,它添加了 name 字段和访问器。

另外,同一个问题是否有任何变体?

【问题讨论】:

  • "对于运行时间小于 O(2^n) 其中 n 是要移动的磁盘数量的河内塔有解决方案吗? - 是的。这叫作弊:-)
  • ...您拿起整个堆栈并立即将其全部移动。不,没有比 2^n 更好的遵循规则的方法了。

标签: java algorithm towers-of-hanoi


【解决方案1】:

鉴于求解河内塔总是需要 2^n - 1 个步骤...不,您不会找到更快的算法,因为打印出这些步骤需要 O(2^n) 很多少计算它们。

【讨论】:

    【解决方案2】:

    我不会证明(正如斯蒂芬所做的那样),但我会尝试直观地解释 2^n-1 是最小值: 在每种状态下,磁盘只有三种可能的移动。 让当前状态表示为有序 seq (1, 1, .. , 1),这样第一个数字表示较大的磁盘在哪里,最后一个数字表示最小的磁盘在哪里。 (1, 1, .., 1) 表示所有磁盘都在位置 1 上。同样从 (1, 1, ..1) 开始只有两种下降状态:(1, 1, ... 2) 和 ( 1, 1, .... 3)。从 (1, 1, ... 2) 有三个下降状态:

    1. 返回 (1, 1, .. 1)
    2. 转到 (1, 1, ..., 3)
    3. 转到 (1, 1,...3, 2)

    如果继续,您将得到图,其中节点是可能的状态,边(转换)是“磁盘移动”。

    您将得到如下所示的图像(如果继续,它将看起来像三角形,顶点将是 (1, 1, ...1), (2, 2, ..2), (3, 3, ...3))。步数其实就是图中的路径。

    如果沿着三角形的边走,2^n-1的步数。所有其他路径的长度相同或更长。

    如果使用策略:将除最大的圆盘外的所有圆盘移至3,然后将大的圆盘移至2,最后将所有形式3移至2,公式可设计如下:

    f(n) =
    f(n -1) // 将除最大值之外的所有元素从 1 移至 3
    + 1 // 最大的从 1 移动到 2
    + f(n -1) // 将所有从 3 移到 2
    ->
    f(n) = 1+ 2 * f(n-1)

    该循环方程的解为您提供该策略所需的步数(恰好是最小步数)

    【讨论】:

      【解决方案3】:

      河内塔的解决方案不可避免地是 2n。然而,在动态规划解决方案中,每个子问题只计算一次,然后通过组合第一个子问题解决方案、当前磁盘移动和第二个子问题解决方案来解决问题。

      因此,生成每个解决方案有两个组件:为当前解决方案分配内存,然后填充该内存。内存分配大约与分配的内存大小无关,并且是昂贵的组件。内存复制与复制的内存大小呈线性关系,虽然速度很快,但作为 Towers 的解决方案,它在 n 中呈指数增长。

      时间 = c1*n + c2*2n,其中 c1 >> c 2。即,它以线性开始,以指数结束。

      Link to article appearing in ACM's SIGCSE Inroads magazine (September 2012)

      【讨论】:

      • 对这个主题有很好的洞察力!一开始我不太相信,直到我在this website found by google search的启发下自己实现它,然后我开始意识到示例程序和回答者是同一个人。谢谢你,@Tim-Rolfe!我很幸运能够在网上追踪你的踪迹。 :-)
      【解决方案4】:

      标准的河内塔问题涉及 3 个钉子。

      但是,如果我们有 k-pegs,时间复杂度将是 O(2^(n/(k-2)))。

      我已经用 4 个钉子和 5 个钉子解决了这个问题,结果的时间复杂度分别是 O(2^(n/2)) 和 O(2^(n/3))

      【讨论】:

        【解决方案5】:

        这个比递归的快大约 7%。它将移动存储在列表中,以便您可以在之后使用它,如果您愿意,可以打印并移除容器。

        ```
        unsigned long i;  
        static const int MAXNUMBEROFDISKS = 32;
        vector<int> pow2Vec;
        uint_fast32_t mPinFrom    = 0;
        uint_fast32_t mNumDisk    = 0;
        unsigned long numDiskLong = 0;
        uint_fast32_t mOffset[MAXNUMBEROFDISKS];
        uint_fast32_t mDir[MAXNUMBEROFDISKS]          = { 0 };
        uint_fast32_t mPositionDisk[MAXNUMBEROFDISKS] = { 0 };
        const uint_fast32_t mRedirectArr[5] = { 2, 0, 1, 2, 0 };
        
        
        
        
        Algos::Algos()
        { 
          for (int i = 0; i < MAXNUMBEROFDISKS; ++i)
          {
            pow2Vec.push_back(pow(2, i));
            mOffset[i] = 1;
          }
        
          for (int i = 1; i < MAXNUMBEROFDISKS; i += 2)
          {
            mDir[i] = 2;
          }
        
          mOffset[0] = 0;
        }
        
        
        
        
        void Algos::calculListBinExperiment(vector<tuple<int, int, int>>& listeFinale, int nbDisk)
        {
          listeFinale.resize(pow2Vec[nbDisk] - 1);
          _BitScanForward(&i, nbDisk);
          for (int noCoup = 1; noCoup < pow2Vec[nbDisk] ; ++noCoup)
          {
            _BitScanForward(&numDiskLong, noCoup);
            mNumDisk = numDiskLong;
            mPinFrom = mPositionDisk[mNumDisk];
            mPositionDisk[mNumDisk] = mRedirectArr[mPositionDisk[mNumDisk] + mDir[mNumDisk + 
            mOffset[i]]];
            listeFinale[noCoup - 1] = make_tuple(mNumDisk, mPinFrom, mPositionDisk[mNumDisk]);
          }
        }
        ```
        

        【讨论】:

        • 您的代码不完整,但似乎calculListBinExperiment 有一个循环,其中noCoup1 运行到2^n,其中n 是磁盘数。那是 O(2^n),而不是 O(2^n) 的改进。并非每一次性能提升都是算法复杂性的提升。
        • 它是完整的(或解释缺少什么)并像我一样进行测试。
        • 你没有抓住重点。我的批评是,您的算法仍然是 O(2^n),即使问题要求 小于 O(2^n)。运行时间的微小差异并不构成算法复杂性的改进。 已经一年多了,但我记得,我提到了“不完整”来解释为什么我写了“出现”而不是更确定的东西。如果您愿意,我可以重写我的评论,以更明确地说明您未能回答问题。
        猜你喜欢
        • 2023-04-10
        • 2014-06-13
        • 2012-09-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多