Java 哪种代码在时间复杂度方面性能更好?

Java 哪种代码在时间复杂度方面性能更好?,java,algorithm,bit-manipulation,time-complexity,Java,Algorithm,Bit Manipulation,Time Complexity,下面是两个查找整数N中1位数的java代码 代码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 = (

下面是两个查找整数N中1位数的java代码

代码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)。所以第二个应该更好


但是我不确定。

第二个算法以恒定的时间运行,这是真的,但相对于第一个算法的常数(5)和运行时间(log2(N)=32,最大N)而言,该常数较大。此外,时间复杂度是通过渐近分析估计的,即算法在大输入(N接近无穷大)下的性能。渐近分析不适用于第二种算法,因为该算法受N拟合32位的限制,并且不能为较大的输入生成正确的值,而第一种算法则会


当将第二个算法扩展到更大的输入时,可以注意到它的时间复杂度随着f(N)的增加而增加。在您的示例中,f(N)=4(至少),因为您可以注意到32位整数中有4个相同的分量。尽管在N停止拟合平台寄存器大小(例如64位)后,第一个算法的时间复杂度也会增加。

第二个算法以恒定时间运行,为真,但该常数相对于第一个算法的常数(5)和运行时间(log2(N)=32,表示最大N)而言较大。此外,时间复杂度是通过渐近分析估计的,即算法在大输入(N接近无穷大)下的性能。渐近分析不适用于第二种算法,因为该算法受N拟合32位的限制,并且不能为较大的输入生成正确的值,而第一种算法则会


当将第二个算法扩展到更大的输入时,可以注意到它的时间复杂度随着f(N)的增加而增加。在您的示例中,f(N)=4(至少),因为您可以注意到32位整数中有4个相同的分量。尽管第一种算法的时间复杂度在N停止拟合平台寄存器大小(例如64位)后也会增加。

因为
O
符号表示输入的大小/长度,对于值
N
的输入,代码1实际上是
O(log2(N))
,因为
log2(N)
N
的长度,以位为单位


循环对
N
中设置的每一位执行一次,因此对于32位
N
,最坏的情况是
N=2^32-1=4294967295
,它的运行次数不超过32次,而不是N次。

因为
O
符号表示输入的大小/长度,对于值
N
的输入,代码1实际上是
O(log2(N))
,因为
log2(N)
N
的长度(以位为单位)


循环对
N
中设置的每一位执行一次,因此对于32位
N
,最坏的情况是
N=2^32-1=4294967295
,它的运行次数不超过32次,而不是N次。

在谈论时间复杂度时,您必须小心。Big-O提供了渐近运行时的度量,这意味着您需要度量任意大的N的运行时。大多数编程语言(默认情况下)不提供任意大的
int
类型,因此我们通常会对这个问题稍加回避,并假装它们提供了

如果我们假设int可以尽可能大,那么第一个代码段就可以工作。在二进制表示的
N
中,它每
1
运行一个循环,因此在最坏的情况下需要
log_2(N)
迭代。所以是O(logn)

如果
int
大于32位,则第二个代码段不起作用(即,它产生错误的结果)。如果我们想象
int
变大以支持渐近分析中所需的更大的
N
s,那么我们需要在程序中增加行数以支持表示
N
所需的额外位。因为程序必须修改才能正确,所以渐近分析实际上是不可能的。任何特定版本的程序都会在O(1)时间内运行,但对于足够大的N,它不会产生正确的结果,因此与第一个代码段进行比较是不公平的

您可以尝试修复第二个程序以适应
N
的大小,而不是使用循环进行硬编码的移位和加法。然后我认为它在O(log(log(N))时间内运行,因为每增加一次移位,聚合的位数就会翻倍

需要记住的一件重要事情是,我们假设移位和位运算仍然是O(1),无论我们将
int
越来越大。这是一个在这些分析中很常见的假设,但在实际的计算机上,机器指令只处理4或8字节的数量,这是不正确的

算法2的动态版本 您的问题是Java,但这里是Python中算法2的正确O(log(log(N))版本(它有任意大的整数)。如果您不懂Python,可以将其视为伪代码——但我认为这无论如何都是相对可读的,并且将其转换为使用java bignums会使其更难理解。它计算
k
,表示
N
所需的位数,四舍五入到2的幂,然后构造移位和掩码的列表计算位所需的时间。这需要O(log(k))时间,并且由于k=log(N),整个算法需要O(log(log(N))时间(此处时间表示按位或算术运算)

def生成操作(k):
ops=[]
掩码=(1>=1
掩码=掩码^(掩码移位)&掩码)
返回N

在谈论时间复杂性时,您必须要注意。Big-O提供了渐近运行时的度量,这意味着您需要度量任意大的N的运行时。大多数编程语言(默认情况下)不提供任意大的
int
类型,因此我们通常会对这个问题进行一些回避,并假装
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