檢測 2 的冪

黃志斌發表於2016-07-14

給定一個整數 x,如何檢測它是不是 2 冪?即是否能夠表示成 2k 的形式,其中 k 是非負整數。

演算法 A

因為 2 的冪不包含 2 以外的素因子,我們有:

  1. x 必須是正整數。
  2. 1 = 20 滿足要求。
  3. 如果 x 是偶數,就一直除以 2,直到 x 變為奇數為止。
  4. 此時,如果 x = 1,則滿足要求,否則就不滿足要求。

對應的 C 語言程式如下所示:

int isPowerOf2a(long x)
{
  if (x <= 0) return 0;
  while (x % 2 == 0) x /= 2;
  return x == 1;
}

演算法 B

在二進位制計算機上,以上對 2 取模和除以 2 的運算可以使用按位與和向右移位運算代替:

int isPowerOf2b(long x)
{
  if (x <= 0) return 0;
  while ((x & 1) == 0) x >>= 1;
  return x == 1;
}

演算法 A 和演算法 B 的時間複雜度都是 O(log n)。

演算法 C

如果 x = 2k,則它的二進位制表示只含有一個 1,後面跟著 k 個 0。而且 x-1 由 k 個 1 組成。因此,(x & x-1) == 0。如果 x 是正整數,且不是 2 的冪,則 x 的二進位制表示包含不止一個 1。x-1 把 x 從 ???10...0 變為 ???01...1,除了最右邊的 1 變為 0 以外,其餘的 1 不變,所以 (x & x-1) != 0。因此,我們有:

int isPowerOf2c(long x) { return x > 0 && (x & x-1) == 0; }

1 10 11 100 101 110 111 1000 1001 1010 x
0 01 10 011 100 101 110 0111 1000 1001 x-1
0 00 10 000 100 100 110 0000 1000 1000 x & x-1

如果 x 是正整數,則 x & x-1 把 x 的二進位制表示最右端的 1 變為 0。

演算法 D

在二進位制計算機上,如果負數以補碼(即反碼加 1)表示,我們有以下演算法:

int isPowerOf2d(long x) { return x > 0 && (x & -x) == x; }

01 010 011 0100 0101 0110 0111 01000 01001 01010 x
11 110 101 1100 1011 1010 1001 11000 10111 10110 -x
01 010 001 0100 0001 0010 0001 01000 00001 00010 x & -x

如果 x 是正整數,則 x & -x 是 2 的冪,其二進位制表示只包含一個 1,且這個 1 是 x 最右端的 1。也就是說,x & -x 把 x 的二進位制表示中除最右端以外的 1 都變為 0。

演算法 C 和演算法 D 的時間複雜度都是 O(1)。

演算法 A 是通用的,可以用於十進位制計算機。演算法 B、C 和 D 只能用於二進位制計算機。演算法 D 還要求負數以補碼錶示。

測試程式

#include <stdio.h>
#include <time.h>

// 上述 4 個函式放在這裡

void test(char id, int (*isPowerOf2)(long), long max)
{
  long z = 0;
  clock_t t = clock();
  for (long i = 0; i <= max; i++)
    if (isPowerOf2(i)) z++;
  printf("isPowerOf2%c: %5.1lf seconds, %ld\n",
    id, (double)(clock()-t)/CLOCKS_PER_SEC, z);
}

int main(void)
{
  long max = 12345678901;
  test('a', isPowerOf2a, max);
  test('b', isPowerOf2b, max);
  test('c', isPowerOf2c, max);
  test('d', isPowerOf2d, max);
  return 0;
}

編譯和執行

$ clang -O isPowerOf2.c && ./a.out

isPowerOf2a:  49.7 seconds, 34
isPowerOf2b:  41.5 seconds, 34
isPowerOf2c:  29.7 seconds, 34
isPowerOf2d:  32.7 seconds, 34

演算法 C 和演算法 D 的速度差不多,而演算法 A 和演算法 B 也不算太慢。

相關文章