下面是两个java代码,它们找到整数N中的1位数。
代码1:
int count = 0;
while (N != 0) {
N = N & (N - 1);
count++;
}
return count;
代码2:
int i = N;
i = i - ((i >>> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
i = (i + (i >>> 4)) & 0x0f0f0f0f;
i = i + (i >>> 8);
i = i + (i >>> 16);
return i & 0x3f;
据我所知,第一种方法的复杂性为O(N),而第二种方法则为O(1)。所以第二个应该更好。
但我不确定。
答案 0 :(得分:4)
在谈论时间复杂性时,你必须要小心。 Big-O提供渐近运行时的度量,这意味着您需要测量任意大N的运行时间。大多数编程语言都不提供(默认情况下)任意大的int
类型,所以通常我们稍微捏造一下这个问题并假装他们这样做。
如果我们假装int可以尽可能大,那么第一个代码片段就可以工作了。它在1
的二进制表示中每N
运行一个循环,因此在最坏的情况下需要log_2(N)
次迭代。所以它是O(log N)。
如果int
大于32位,则第二个代码段不起作用(即产生错误的结果)。如果我们想象int
越来越大,以支持我们在渐近分析中需要的更大N
,那么我们在程序中需要更多行来支持表示N
所需的额外位。因为程序必须改变为正确,所以渐近分析是不可能的。程序的任何特定版本都在O(1)时间内运行,但是对于足够大的N,它不能产生正确的结果,因此它与第一个代码片段不是一个公平的比较。
您可以尝试通过使用循环来修复第二个程序以适应N
的大小而不是硬编码的移位和添加。然后我认为它在O(log(log(N)))时间内运行,因为每增加一次移位就会使聚合的位数加倍。
要记住的一件重要事情是,我们假设移位和按位运算仍为O(1),无论我们使int
越来越大。这是在这些分析中非常常见的假设,但在机器指令仅处理4或8字节数量的实际计算机上并不是这样。
你的问题是Java,但这是Python中算法2的正确O(log(log(N))版本(它有任意大的int)。如果你不知道,你可以将它视为伪代码Python - 但我认为无论如何这都是相对可读的,并且将其转换为使用java bignums将使其更难理解。它计算k
,表示N
所需的位数,四舍五入到2的幂,然后构造一个计数位所需的移位和掩码列表。这需要O(log(k))时间,并且由于k = log(N),整个算法需要O(log( log(N))时间(这里的时间表示按位或算术运算)。
def generate_ops(k):
ops = []
mask = (1 << k) - 1
while k:
ops.append((k, mask))
k >>= 1
mask = mask ^ (mask << k)
return reversed(ops)
def bit_count(N):
k = 1
while (1 << k) < N:
k *= 2
for shift, mask in generate_ops(k):
N = (N & mask) + ((N >> shift) & mask)
return N
答案 1 :(得分:2)
由于 <?php
//Saving Data to Image Table
function saveImageData($data,$imgFile){
$project_id = isset($data['project_id']) ? mysql_real_escape_string($data['project_id']) : "";
$image_id = isset($data['image_id']) ? mysql_real_escape_string($data['image_id']) : "";
//$user_id = isset($data['user_id']) ? mysql_real_escape_string($data['user_id']) : "";
$image_name = isset($data['image_name']) ? mysql_real_escape_string($data['image_name']) : "";
if(isset($imgFile['tmp_name']) && filesize($imgFile['tmp_name'])>0)
saveImageFile($imgFile['tmp_name'],$image_id."_".$project_id."_".$category_id."_".$image_name);
$category_id = isset($data['category_id']) ? mysql_real_escape_string($data['category_id']) : "";
$houssup_id = isset($data['houssup_id']) ? mysql_real_escape_string($data['houssup_id']) : "";
$location_id = isset($data['location_id']) ? mysql_real_escape_string($data['location_id']) : "";
// Insert data into data base
$sql = "INSERT INTO `".DB_NAME."`.`image_table`(`project_id`, `image_id`,`image_name`,`category_id`,`houssup_id`,`location_id`)
VALUES(NULL,'$image_id','$image_name','$category_id','$houssup_id','$location_id')";
echo $sql;
if(mysql_query($sql))
return true;
else
return false;
}
表示法指的是输入的 size / length ,因此对于值O
的输入,代码1实际上是{{ 1}},因为N
是位O(log2(N))
的长度。
对log2(N)
中设置的每个位执行一次循环,因此对于32位N
,最坏情况为N
运行时间不超过32次而不是N次。
答案 2 :(得分:0)
第二种算法在恒定时间内运行,为真,但该常数相对于第一算法的常数(5)和运行时间(log2(N)= 32,对于最大N)较大。此外,通过渐近分析来估计时间复杂度,即算法如何针对大输入(N接近无穷大)执行。渐近分析不适用于第二种算法,因为该算法受到N拟合32位的限制,并且对于较大的输入不能产生正确的值,而第一种算法确实如此。
当将第二算法扩展到更大的输入时,可以注意到它的时间复杂度随着f(N)的增加而增长。在您的示例中,f(N)= 4(至少),因为您可以注意到32位整数中的4个相同组件。尽管在N停止拟合平台寄存器大小(例如64位)之后,第一算法的时间复杂度也增加。
答案 3 :(得分:0)
如上所述,这是一个毫无意义的问题。第二段代码不是算法,而是针对特定大小的整数的算法的特化。在某种程度上,第一段代码也是如此,虽然我们原则上可以选择任何大小的int
,但必须有一个选择,因此输入的大小不能改变。因此,任何大O都没有n
,任何东西都必须是O(1),因为我们看错了东西,所以没有给我们任何信息。
对于这两种情况,都有一种适用于任何大小的位向量的通用算法, 可用于查看。
第一种算法显然最多循环次数与位数一样多。因此,对于一般位向量n
次,但请注意,这不会转换为O(n)“传统的基本步骤”,因为它是用n位位向量计算的。但是,如果算上宽字步骤(这是典型的问题),那么显然有O(n)。
第二种算法的一般形式比较棘手,TAOCP第4A卷给出了一些提示,按位技巧和技巧。它实际上并没有在那里定义,但它应该是显而易见的:一个加法树,通过添加相邻的popcnt对,在每一层上我们拥有popcnt的大小加倍,其中每个层以恒定数量的宽字步骤实现(“魔术面具”是μ k 所以我们可以假装存在这些常量)。由于加倍,层数为ceil(log2(n)),因此总算法采用O(log n)宽字步骤。