当前位置: 首页 > 知识库问答 >
问题:

如何用逐步分析法识别递归回溯算法的时间和空间复杂度

欧阳绪
2023-03-14
public class Solution {
    public int NumWays { get; set; }
    public int TotalNQueens(int n) {
        if (n <= 0)
        {
            return 0;
        }
        
        NumWays = 0;
        
        bool[][] board = new bool[n][];
        for (int i = 0; i < board.Length; i++)
        {
            board[i] = new bool[n];
        }
        
        Solve(n, board, 0);
        
        return NumWays;
    }
    
    private void Solve(int n, bool[][] board, int row)
    {
        if (row == n)
        {
            // Terminate since we've hit the bottom of the board
            NumWays++;
            return;
        }
        
        for (int col = 0; col < n; col++)
        {
            if (CanPlaceQueen(board, row, col))
            {
                board[row][col] = true; // Place queen
                Solve(n, board, row + 1);
                board[row][col] = false; // Remove queen
            }
        }
    }
    
    private bool CanPlaceQueen(bool[][] board, int row, int col)
    {
        // We only need to check diagonal-up-left, diagonal-up-right, and straight up. 
        // this is because we should not have a queen in a later row anywhere, and we should not have a queen in the same row
        for (int i = 1; i <= row; i++)
        {
            if (row - i >= 0 && board[row - i][col]) return false;
            if (col - i >= 0 && board[row - i][col - i]) return false;
            if (col + i < board[0].Length && board[row - i][col + i]) return false;
        }
        
        return true;
    }
}

共有1个答案

郑锦
2023-03-14

首先,递归回溯算法不一定都在o(n!)中:当然,这取决于html" target="_blank">算法,而且可能更糟。话虽如此,一般的方法是写下时间复杂度t(n)的递推关系,然后试图求解它或至少刻画它的渐近行为。

我们感兴趣的是最坏的情况、最好的情况还是平均的情况?输入参数是什么?

在本例中,假设我们要分析最坏情况下的行为,并且相关的输入参数是solve方法中的n

private void Solve(int n, bool[][] board, int row)
    {
        if (row == n) // base case
        {
           [...] // O(1)
           return;
        }
        
        for (...) // loop n times
        {
            if (CanPlaceQueen(board, row, col)) // O(k)
            {
                [...] // O(1)
                Solve(n, board, row + 1); // recurse on k - 1 = n - (row + 1)
                [...] // O(1)
            }
        }
    }
T(0) = 1         // base case
T(k) = k *       // loop n times 
       (O(k) +   // if (CanPlaceQueen(...))
       T(k-1))   // Solve(n, board, row + 1)
     = k T(k-1) + O(k)
T(n) = n T(n-1) + f(n)
T(n) = n!(T(0) + Sum { f(i)/i!, for i = 1..n })

我们可以通过归纳很容易地证明:

T(n) = n T(n-1) + f(n)                                          // by def.
     = n((n-1)!(T(0) + Sum { f(i)/i!, for i = 1..n-1 })) + f(n) // by ind. hypo.
     = n!(T(0) + Sum { f(i)/i!, for i = 1..n-1 }) + f(n)/n!)
     = n!(T(0) + Sum { f(i)/i!, for i = 1..n })                 // qed

现在,我们不需要精确解;我们只需要当n接近无穷大时的渐近行为。

让我们来看看无穷级数

Sum { f(i)/i!, for i = 1..infinity }
L = lim { | (f(n+1)/(n+1)!) / (f(n)/n!) |, for n -> infinity }
  = lim { | f(n+1) / (f(n)(n+1)) |, for n -> infinity }
  = 0  // if f is a polynomial
  < 1, and hence the series converges
T(n) -> n!(T(0) + Sum { f(i)/i!, for i = 1..infinity })
      = T(0) n!, if f is a polynomial
T(n) ∈ Θ(n!)

对于不同形式的递归关系的类似分析,请参见此处。

我在代码的注释中犯了一个错误(我将留下它,因为它仍然有指导意义)。实际上,循环和循环中所做的工作并不依赖于k=n-row,而是依赖于初始值n(为了清楚起见,我们将其称为n0)。

所以递推关系变成

T(k) = n0 T(k-1) + n0
T(k) = n0^k (T(0) + Sum { n0^(1-i), for i = 1..k })
T(k) = k^k (T(0) + Sum { n0^(1-i), for i = 1..k })
     ∈ Θ(k^k)
 类似资料:
  • 如何计算这些回溯算法的时间复杂度,它们是否具有相同的时间复杂度?如果不一样怎么办?请详细解释并感谢您的帮助。 我实际上有点困惑,对于断字(b),复杂度是O(2n),但对于哈密顿循环,复杂度是不同的,对于打印相同字符串的不同排列,以及对于解决n皇后问题,复杂度是不同的。

  • 所以我在理解为什么递归DFS和迭代DFS的时间复杂度相同时遇到了一些问题,也许有人能给我一个简单的解释? 提前谢了。

  • 对于回溯算法,计算空间(堆栈空间)和时间复杂度的好方法是什么? 我们希望输出组合的长度正好是K,并且sol集必须是唯一的 输入:arr:[1,2,3,4,5]k:4 输出: [1,2,3,4]//[2,1,3,4]无效,因为它==[1,2,3,4] 最差的时间复杂度是O(n^k),其中n是数组的长度。

  • 我有一个算法可以检查是否可以解决游戏行。游戏行是一个正整数数组,其中最后一个元素为 0。游戏标记从索引 0 开始,沿着数组移动它所在的整数指示的步数。 例如,[1,1,0]返回true,而[1,2,0]返回false。标记也可以向左或向右移动以解决游戏。也就是说,[3,3,2,2,0]是可解的。 我尝试了一些游戏行示例,并计算了最坏情况下的时间复杂度: 其他情况下给我的数字,我找不到与输入大小的关

  • 我在理解算法的时间复杂性方面有问题。 让我们举第一个例子,用这个算法在二叉搜索树中进行搜索: 那么,如何计算这个时间复杂度呢? null

  • 3. 算法的时间复杂度分析 解决同一个问题可以有很多种算法,比较评价算法的好坏,一个重要的标准就是算法的时间复杂度。现在研究一下插入排序算法的执行时间,按照习惯,输入长度LEN以下用n表示。设循环中各条语句的执行时间分别是c1、c2、c3、c4、c5这样五个常数[23]: void insertion_sort(void) 执行时间 { int i, j, key; for (j = 1;