列出该集的所有子集的时间复杂度?

时间:2013-12-20 20:20:39

标签: java big-o bit-manipulation time-complexity powerset

我在网上发现了许多具有O(2 ^ n)复杂度的解决方案。有人可以帮我弄清楚下面给出的代码的时间复杂性。它也涉及很多位操作,我在那个区域真的很弱,所以我没有完全掌握代码。如果有人能够解释代码,那就太好了。

private static void findSubsets(int array[])
{
  int numOfSubsets = 1 << array.length; 

  for(int i = 0; i < numOfSubsets; i++)
  {
    int pos = array.length - 1;
    int bitmask = i;

    System.out.print("{");
    while(bitmask > 0)
    {
      if((bitmask & 1) == 1)
         System.out.print(array[pos]+",");
     {
        bitmask >>= 1;
        pos--;
     }
     System.out.print("}");
  }
}

这是最佳解决方案吗?

4 个答案:

答案 0 :(得分:7)

此代码的工作原理是使用二进制数与正好n位和一组n个元素的子集之间的连接。如果将集合中的每个元素分配给单个位并将“1”表示“包含子集中的元素”而将“0”表示“从子集中排除元素”,则可以轻松地在两者之间进行转换。例如,如果集合包含a,b和c,那么100可能对应于子集a,011对应于子集bc等。尝试使用此洞察再次阅读代码。

就效率而言,上述代码在实际和理论上都非常快。任何列出子集的代码都必须花费大量时间来列出这些子集。所需时间与必须列出的元素总数成比例。此代码每列出一个项目花费O(1)工作,因此渐近最优(当然,假设没有那么多元素溢出使用的int)。

此代码的总复杂性可以通过计算打印的总元素数来确定。我们可以通过一些数学来解决这个问题。注意,有n个选择0个大小为0的子集,n选择1个大小为1的子集,n选择2个大小为2的子集,等等。因此,打印的元素总数由

给出
  

C = 0×(n选择0)+ 1×(n选择1)+ 2×(n选择2)+ ... + n×(n选择n)

注意(n选择k)=(n选择n - k)。因此:

  

C = 0×(n选择n)+ 1×(n选择n - 1)+ 2×(n选择n - 2)+ ... + n×(n选择0)

如果我们将这两者加在一起,我们就得到了

  

2C = n×(n选择0)+ n×(n选1)+ ... + n×(n选n)

     

= n×(n选择0 + n选择1 + ... + n选择n)

二项式定理表明带括号的表达式是2 n ,所以

  

2C = n2 n

所以

  

C = n2 n-1

因此,确实打印了n2 n-1 元素,因此该方法的时间复杂度为Θ(n 2 n )。

希望这有帮助!

答案 1 :(得分:5)


时间复杂度:

这是另一种导出时间复杂度的方法(与@templatetypedef相比)。

M 成为代码中的步骤总数。你的外部for循环运行 2 N 次,内部运行 log(i)次,所以我们有:

enter image description here

2 提升到上述等式的每一边并简化:

enter image description here

取上述等式两边的 log ,并使用Sterling Approximation( Log(x!)~xLog(x) - x

enter image description here


位操作:

要解决位操作中的弱点,实际上并不需要它。它在您的代码中以三种方式使用,所有这些都可以用较少混淆的函数替换:

  1. 2的力量:1 << array.length)→(Math.pow(2, array.length)
  2. 除以2:x >>= 1)→(x /= 2
  3. 模数2: (x & 1)(x % 2)

  4. 代码说明:

    另外,为了解决代码正在做的事情,它实际上是使用显示here的方法将每个数字(0到2 N )转换为二进制形式,并作为@templatetypedef states,1表示相应的元素在集合中。以下是使用此方法将156转换为二进制的示例:

    enter image description here

    以您的代码为例:

    pos = array.length - 1;
    bitmask = 156;                              // as an example, take bitmask = 156
    while(bitmask > 0){
        if(bitmask%2 == 1)                      // odd == remainder 1, it is in the set
             System.out.print(array[pos]+",");
        bitmask /= 2;                           // divide by 2
        pos--;
    }
    

    通过对所有位掩码执行此操作(0到2 N ),您将找到所有唯一可能的集合。


    <强> 编辑:

    这里看一下英镑近似中的比率(n2 n / log 2 (2 n !)你可以看到它当n变大时,快速接近统一:

    enter image description here

答案 2 :(得分:1)

我们说array.length是n。此代码的工作原理是根据从0到2 ^ n的所有数字的二进制表示来选择或排除集合中的元素,这些数字正是集合的所有组合。

外部for循环的复杂度为O(2 ^ n),因为numOfSubsets = 1 << array.length为2 ^ n。对于内部循环,您正在向右移动,最坏的情况是111 ... 1(n位设置为1),因此在最坏的情况下您将获得O(n)复杂度。因此总复杂度将为O(n *(2 ^ n))。

答案 3 :(得分:1)

https://github.com/yaojingguo/subsets提供了两种算法来解决子集问题。 void Notepad::on_pushButton_10_clicked(){ ui->lineEdit->setText("test"); } 算法与问题中给出的代码相同。 Iter算法使用递归来访问每个可能的子集。两种算法的时间复杂度为Recur。在Θ(n*2^n)算法中,Iter语句执行1次。 n*2^n语句执行2(基于@ templatetypedef&#39; s分析)。使用n*2^(n-1)表示a的费用。并使用1表示b的费用。总费用为2

n*2^n*a + n*2^(n-1)*b

以下是if ((i & (1 << j)) > 0) // 1 list.add(A[j]); // 2 算法的主要逻辑:

Recur

对帐单result.add(new ArrayList<Integer>(list)); // 3 for (int i = pos; i < num.length; i++) { // 4 list.add(num[i]); dfs(result, list, num, i + 1); list.remove(list.size() - 1); } 的费用3n*2^(n-1)*b相同。另一个成本是1循环。每个循环迭代包括三个函数调用。 4总共执行4次。使用2^n表示d的费用。总费用为4。下图是此算法的递归树,其中包含2^n*d + n*2^(n-1)*b。更精确的分析需要以不同方式处理{1, 2, 3, 4}叶节点。

2^(n-1)

比较这两种算法的复杂性是将 Ø --- 1 --- 2 --- 3 --- 4 | | |- 4 | |- 3 --- 4 | |- 4 |- 2 --- 3 --- 4 | |- 4 |- 3 --- 4 |- 4 (1)与n*2^n*a(2)进行比较。将(1)除以(2),我们得到2^n*d。如果n * a / d小于n*a,则dIter快。我使用Recur来衡量这两种算法的效率。以下是一次运行的结果:

Driver

它表明,对于小n: 16 Iter mills: 40 Recur mills: 19 n: 17 Iter mills: 78 Recur mills: 32 n: 18 Iter mills: 112 Recur mills: 10 n: 19 Iter mills: 156 Recur mills: 149 n: 20 Iter mills: 563 Recur mills: 164 n: 21 Iter mills: 2423 Recur mills: 1149 n: 22 Iter mills: 7402 Recur mills: 2211 nRecur快。