上界固定时操作的阶数

order of an operation when upper bound is fixed

本文关键字:操作 定时      更新时间:2023-10-16

我最近参加了一次面试,被要求找出所提供的整数的位数。我写的是这样的:

#include <iostream>
using namespace std;
int givemCountOnes (unsigned int X) {
  int count =0;
  while (X != 0 ) {
    if(X & 1)
      count++;
   X= X>>1;
  }
 return count;
}
int main() {
cout << givemCountOnes (4);
return 0;
}

我知道有更好的方法,但这不是问题所在。

问题是,这个程序的复杂性是多少?

因为它表示输入的比特数,所以人们说这是O(n),其中n是输入的比特数。

但是我觉得由于上界是sizeof(unsigned int),也就是说64位,我应该说顺序是0(1)。

我错了吗?

复杂度为0 (N)。复杂度随所使用的类型(unsigned int)的大小线性上升。

上界并不重要,因为它可以在将来的任何时候被扩展。这也无关紧要,因为总是有一个上限(内存大小,宇宙中的原子数量),然后所有东西都可以被认为是0(1)。

我将给上面的问题添加一个更好的解决方案。

在Loop

中使用以下步骤
x = x & (x-1);

这将每次一个移除最右边的ON位。

所以你的循环将在最大运行,只要有一个ON位。当数字接近0时终止。

因此复杂度从0 (int的位数)提高到0 (on的位数)。

0表示法用于区分n不同值之间的区别。在这种情况下,n将是位数,因为(在您的情况下)位数将改变执行计算所需的(相对)时间。所以O(n)是正确的-一个1位的整数需要1个单位时间,一个32位的整数需要32个单位时间,一个64位的整数需要64个单位时间。

实际上,你的算法并不依赖于数字中的实际位数,而是依赖于数字中最高位的位数,但这是另一回事。然而,由于我们通常将0视为"最坏情况",所以它仍然是O(n),其中n是整数中的位数。

我真的想不出任何方法比O更好——我可以想到提高循环迭代次数的方法(例如使用256个条目表,一次处理8位),但它仍然是"更大的数据->更长的时间"。因为O(n)和O(n/2)或O(n/8)都是一样的(只是后一种情况的总时间是前一种情况的1/8)。

大0符号描述了在最坏情况下算法步骤的计数。在这种情况下,最后一位是1。因此,当你传递n个比特数作为输入时,将会有n次迭代/步骤。

想象一个类似的算法,搜索列表中1的个数。复杂度是O(n)其中n是列表长度。根据你的假设,如果你总是传递固定大小的列表作为输入,那么算法复杂度将变成O(1),这是不正确的。然而,如果你在算法中固定位长度:即像for (int i = 0; i < 64; ++i) ...这样的东西,那么它将具有O(1)复杂度,因为它做了64次O(1)操作,你可以忽略这里的常数。否则O(c*n) = O(n), O(c) = O(1),其中c为常数。

希望这些例子对你有帮助。顺便说一句,有0(1)个解决方案,我记得的时候会贴出来的:)

有一件事需要澄清:对整数进行操作的复杂性。在这个例子中不清楚,当你在int上工作时,这是你的机器上的自然字长,它的复杂性似乎只有1。

但是o符号是关于大数据量和大任务的,比如说你有一个n位整数,其中n大约是4096左右。在这种情况下,复杂度加法,减法和移位至少是O(n)复杂度,所以你的算法应用于这样的整数将是O(n²)复杂度(n个操作的O(n)复杂度应用)。

不移动整数的直接计数算法(假设一位测试是O(1))给出O(n log(n))复杂度(它涉及对log(n)大小的整数进行最多n次加法)。

但是对于固定长度的数据(这是C的int)大O分析是毫无意义的,因为它基于变量长度的输入数据,更确切地说,几乎任何长度的数据,直到无穷大。