【问题标题】:Binary search bit operation二进制搜索位操作
【发布时间】:2016-04-24 13:00:14
【问题描述】:

我想知道这两种实现中哪一种更快,为什么?

int N, A[N];

int binary_search(int val)
{
    int i, step;
    for (step = 1; step < N; step <<= 1)
        ;
    for (i = 0; step; step >>= 1)
        if (i + step < N && A[i + step] <= val)
           i += step;
}

还有一个正常的实现,您可以找到mid=(st+dr)/2,然后根据A[mid] 的值和您的值在数组的左侧或右侧应用?

【问题讨论】:

  • “我想知道这两种实现中哪一种更快”——然后测量一下,也许?
  • 注意这是不可编译的C代码;你不能有一个可变修改的全局数组,A[N] 是可变修改的(因为 N 显然是一个变量)。
  • 我尝试测量它,但我不知道为什么我得到几乎相同的时间,但有人说第一个更快,我想知道为什么? PS:顺磁羊角面包如果你没有什么好说的,请坚持自己发表这样的回应。你没有帮助我。当我问你一些事情时,发布这样的回复是没有用的。我更喜欢在来这里之前尝试我所知道的,我向你保证我试过你所说的,但我看不出很明显的区别
  • for (step = 1; step &lt; N; step &lt;&lt;= 1);rounding up to nearest power of 2 stackoverflow.com/q/1322510/995714 stackoverflow.com/q/364985/995714 完全相同
  • 代码如何报告缺少值?实际上,由于函数中没有return,它如何报告找到值的位置?该代码真的能处理正常实现处理的所有边缘情况吗?

标签: binary-search


【解决方案1】:

伪代码到C代码的转换

该问题询问的是不是二进制搜索函数的有效实现的代码片段,因为它不处理数组中不存在所寻找的值的情况。

可以像这样将代码转换为可工作的 C 代码函数:

int binary_search(int N, const int A[N], int val)
{
    int i, step;
    for (step = 1; step < N; step <<= 1)
        ;
    for (i = 0; step; step >>= 1)
        if (i + step < N && A[i + step] <= val)
           i += step;
    if (A[i] != val)
        i = -1;
    return i;
}

这已经在严格的测试工具中进行了测试,并且它产生了与在同一工具中测试的其他二进制搜索变体相同的答案。

与其他二分搜索算法的比较

我碰巧在我的与 SO 问题相关的材料文件夹中潜伏着 4 个其他二进制搜索实现。给定一个可以在给定范围内生成随机数的程序,以及一些支持脚本,加上可以将经过的时间报告为微秒的计时代码(它在 Mac OS X 上使用gettimeofday(),这让一些人很反感,但这已经足够了在这种情况下),我生成了这个计时程序。它包括算法:

  • BinSearch_A — 在数组 X[0..N-1] 中找到与 T 匹配的任意索引 P。
  • BinSearch_B — 在数组 X[0..N-1] 中找到与 T 匹配的最小索引 P。
  • BinSearch_C — 在数组 X[0..N-1] 中找到与 T 匹配的最大索引 P。
  • BinSearch_D — 在数组 X[0..N-1] 中找到与 T 匹配的最小 (L) 和最大 (U) 索引。
  • BinSearch_E — 在数组 X[0..N-1] 中找到与 T 匹配的任意索引 P(使用上面修正的问题中的算法)。

请注意,算法 B、C 和 D 解决的问题比 A 和 E 解决的问题严格得多,因此可以预期 B、C 和 D 将比 A 和 E 慢。

大纲代码(binsearch-speed-1.c)

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>

typedef struct Pair
{
    int lo;
    int hi;
} Pair;

extern Pair BinSearch_D(int N, const int X[N], int T);
extern int BinSearch_A(int N, const int X[N], int T);
extern int BinSearch_B(int N, const int X[N], int T);
extern int BinSearch_C(int N, const int X[N], int T);
extern int BinSearch_E(int N, const int X[N], int T);

#ifndef lint

extern const char jlss_id_modbinsearch_c[];
const char jlss_id_modbinsearch_c[] = "@(#)$Id$";
#endif

int BinSearch_A(int N, const int X[N], int T)
{
    int L = 0;
    int U = N-1;
    while (1)
    {

        if (L > U)
            return -1;
        int M = (L + U) / 2;
        if (X[M] < T)
            L = M + 1;
        else if (X[M] > T)
            U = M - 1;
        else
            return M;
    }
    assert(0);
}

int BinSearch_B(int N, const int X[N], int T)
{
    int L = -1;
    int U = N;
    while (L + 1 != U)
    {

        int M = (L + U) / 2;
        if (X[M] < T)
            L = M;
        else
            U = M;
    }
    assert(L+1 == U && (L == -1 || X[L] < T) && (U >= N || X[U] >= T));
    int P = U;
    if (P >= N || X[P] != T)
        P = -1;
    return P;
}

int BinSearch_C(int N, const int X[N], int T)
{
    int L = -1;
    int U = N;
    while (L + 1 != U)
    {

        int M = (L + U) / 2;
        if (X[M] <= T)
            L = M;
        else
            U = M;
    }
    assert(L+1 == U && (L == -1 || X[L] <= T) && (U >= N || X[U] > T));
    int P = L;
    if (P < 0 || X[P] != T)
        P = -1;
    return P;
}

Pair BinSearch_D(int N, const int X[N], int T)
{
    int L_lo = -1;
    int L_hi = N;
    int U_lo = -1;
    int U_hi = N;

    while (L_lo + 1 != L_hi || U_lo + 1 != U_hi)
    {
        if (L_lo + 1 != L_hi)
        {

            int L_md = (L_lo + L_hi) / 2;
            if (X[L_md] < T)
                L_lo = L_md;
            else
                L_hi = L_md;
        }
        if (U_lo + 1 != U_hi)
        {

            int U_md = (U_lo + U_hi) / 2;
            if (X[U_md] <= T)
                U_lo = U_md;
            else
                U_hi = U_md;
        }
    }

    assert(L_lo+1 == L_hi && (L_lo == -1 || X[L_lo] < T) && (L_hi >= N || X[L_hi] >= T));
    int L = L_hi;
    if (L >= N || X[L] != T)
        L = -1;
    assert(U_lo+1 == U_hi && (U_lo == -1 || X[U_lo] <= T) && (U_hi >= N || X[U_hi] > T));
    int U = U_lo;
    if (U < 0 || X[U] != T)
        U = -1;

    return (Pair) { .lo = L, .hi = U };
}

int BinSearch_E(int N, const int X[N], int T)
{
    int i, step;
    for (step = 1; step < N; step <<= 1)
        ;
    for (i = 0; step; step >>= 1)
        if (i + step < N && X[i + step] <= T)
            i += step;
    if (X[i] != T)
        i = -1;
    return i;
}

#include "timer.h"

static const int numbers[] =
{
    10000, 10002, 10003, 10003, 10003, 10004, 10006, 10010, 10011, 10015,
    10016, 10020, 10023, 10024, 10029, 10029, 10030, 10031, 10032, 10035,
    10036, 10036, 10037, 10037, 10038, 10041, 10043, 10044, 10046, 10049,
    10066, 10066, 10069, 10070, 10071, 10074, 10079, 10080, 10085, 10086,
    10087, 10089, 10090, 10090, 10090, 10091, 10092, 10094, 10095, 10095,

…省略了 990 行类似的行…

    29869, 29870, 29872, 29872, 29874, 29877, 29877, 29882, 29884, 29888,
    29895, 29898, 29899, 29908, 29912, 29922, 29923, 29924, 29925, 29929,
    29934, 29936, 29938, 29939, 29941, 29942, 29943, 29943, 29944, 29945,
    29947, 29949, 29951, 29953, 29956, 29958, 29959, 29959, 29964, 29965,
    29965, 29966, 29968, 29969, 29981, 29983, 29983, 29984, 29984, 29988,
};
enum { NUM_NUMBERS = sizeof(numbers) / sizeof(numbers[0]) };

static void check_sorted(const char *a_name, int size, const int array[size])
{
    int ok = 1;
    for (int i = 1; i < size; i++)
    {
        if (array[i-1] > array[i])
        {
            fprintf(stderr, "Out of order: %s[%d] = %d, %s[%d] = %d\n",
                    a_name, i-1, array[i-1], a_name, i, array[i]);
            ok = 0;
        }
    }
    if (!ok)
        exit(1);
}

static int BinSearch_D1(int size, const int array[size], int value)
{
    Pair p = BinSearch_D(size, array, value);
    return p.lo;
}

typedef int (*BinSearch)(int size, const int data[size], int value);

static void time_search(const char *a_name, int size, const int array[size],
                        BinSearch function)
{
    Clock clk;
    clk_init(&clk);
    int x0 = array[0] - 1;
    int x1 = array[size-1] + 2;
    long long vsum = 0;

    clk_start(&clk);
    for (int i = x0; i < x1; i++)
    {
        int index = (*function)(size, array, i);
        vsum += (index == -1) ? index : array[index];
    }
    clk_stop(&clk);
    char buffer[32];
    printf("%s: (%d)  %lld   %s\n", a_name, size, vsum,
           clk_elapsed_us(&clk, buffer, sizeof(buffer)));
}

int main(void)
{
    check_sorted("numbers", NUM_NUMBERS, numbers);

    for (int i = 0; i < 10; i++)
    {
        time_search("BinSearch_A", NUM_NUMBERS, numbers, BinSearch_A);
        time_search("BinSearch_B", NUM_NUMBERS, numbers, BinSearch_B);
        time_search("BinSearch_C", NUM_NUMBERS, numbers, BinSearch_C);
        time_search("BinSearch_D", NUM_NUMBERS, numbers, BinSearch_D1);
        time_search("BinSearch_E", NUM_NUMBERS, numbers, BinSearch_E);
    }

    return 0;
}

此代码适用于包含 10,000 个随机数的数组,重复次数在 10,000 到 29,999 之间。这意味着该范围内大约一半的可能值存在于数组中。对于每个函数,它计算从数组中的最小数字 - 1 到数组中的最大数字 + 1 范围内的每个值的索引。因为当可能存在多个匹配时,算法会合法地返回不同的索引,所以测试代码对找到的值求和(并为每次搜索失败减去 1)。输出标识所花费的时间(以微秒为单位),并打印数组大小和计算值。计算的原因之一是确保优化器不会优化太多。

我还生成了第二个程序 (binsearch-speed-2.c),其代码相同,但包含 1,000,000 到 3,000,000 范围内的 1,000,000 个数字。由于binsearch-speed-1.c 的时间在 0.7 - 1.4 毫秒范围内,数据量比我认为舒适的要小一些,所以我将问题大小增加了 100 以生成相应更大的时间。然而,更大规模的问题改变了算法的相对时间(这就是你看到这个的原因)。

测试运行的是一台老式 MacBook Pro(2011 年初),配备 2.3 GHz Intel Core i7 CPU 和 16 GiB 的 1333 MHz DDR3 内存,运行 Mac OS X 10.11.4,并使用 GCC 5.3.0。您的里程会有所不同!

示例编译命令行:

$ gcc -O3 -g -I$HOME/inc -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes \
>     -Wold-style-definition -Werror binsearch-speed-2.c -o binsearch-speed-2 \
>     -L$HOME/lib/64 -ljl
$

计时函数在引用的库中。

原始结果 binsearch-speed-1(大小 10,000)

BinSearch_A: (10000)  158341368   0.000817
BinSearch_B: (10000)  158341368   0.001076
BinSearch_C: (10000)  158341368   0.001006
BinSearch_D: (10000)  158341368   0.001337
BinSearch_E: (10000)  158341368   0.000787
BinSearch_A: (10000)  158341368   0.000771
BinSearch_B: (10000)  158341368   0.001540
BinSearch_C: (10000)  158341368   0.001003
BinSearch_D: (10000)  158341368   0.001344
BinSearch_E: (10000)  158341368   0.000791
BinSearch_A: (10000)  158341368   0.000799
BinSearch_B: (10000)  158341368   0.001078
BinSearch_C: (10000)  158341368   0.001008
BinSearch_D: (10000)  158341368   0.001386
BinSearch_E: (10000)  158341368   0.000802
BinSearch_A: (10000)  158341368   0.000774
BinSearch_B: (10000)  158341368   0.001083
BinSearch_C: (10000)  158341368   0.001176
BinSearch_D: (10000)  158341368   0.001495
BinSearch_E: (10000)  158341368   0.000907
BinSearch_A: (10000)  158341368   0.000817
BinSearch_B: (10000)  158341368   0.001080
BinSearch_C: (10000)  158341368   0.001007
BinSearch_D: (10000)  158341368   0.001357
BinSearch_E: (10000)  158341368   0.000786
BinSearch_A: (10000)  158341368   0.000756
BinSearch_B: (10000)  158341368   0.001080
BinSearch_C: (10000)  158341368   0.001899
BinSearch_D: (10000)  158341368   0.001644
BinSearch_E: (10000)  158341368   0.000791
BinSearch_A: (10000)  158341368   0.000770
BinSearch_B: (10000)  158341368   0.001087
BinSearch_C: (10000)  158341368   0.001014
BinSearch_D: (10000)  158341368   0.001378
BinSearch_E: (10000)  158341368   0.000793
BinSearch_A: (10000)  158341368   0.001415
BinSearch_B: (10000)  158341368   0.001160
BinSearch_C: (10000)  158341368   0.001006
BinSearch_D: (10000)  158341368   0.001336
BinSearch_E: (10000)  158341368   0.000786
BinSearch_A: (10000)  158341368   0.000763
BinSearch_B: (10000)  158341368   0.001079
BinSearch_C: (10000)  158341368   0.001012
BinSearch_D: (10000)  158341368   0.001309
BinSearch_E: (10000)  158341368   0.000796
BinSearch_A: (10000)  158341368   0.000769
BinSearch_B: (10000)  158341368   0.001094
BinSearch_C: (10000)  158341368   0.001029
BinSearch_D: (10000)  158341368   0.001397
BinSearch_E: (10000)  158341368   0.000800

原始结果 binsearch-speed-2(大小 1,000,000)

BinSearch_A: (1000000)  1573140220897   0.081161
BinSearch_B: (1000000)  1573140220897   0.137057
BinSearch_C: (1000000)  1573140220897   0.132743
BinSearch_D: (1000000)  1573140220897   0.166290
BinSearch_E: (1000000)  1573140220897   0.189696
BinSearch_A: (1000000)  1573140220897   0.083374
BinSearch_B: (1000000)  1573140220897   0.136225
BinSearch_C: (1000000)  1573140220897   0.128654
BinSearch_D: (1000000)  1573140220897   0.168078
BinSearch_E: (1000000)  1573140220897   0.190977
BinSearch_A: (1000000)  1573140220897   0.083391
BinSearch_B: (1000000)  1573140220897   0.135630
BinSearch_C: (1000000)  1573140220897   0.131179
BinSearch_D: (1000000)  1573140220897   0.168578
BinSearch_E: (1000000)  1573140220897   0.188785
BinSearch_A: (1000000)  1573140220897   0.083069
BinSearch_B: (1000000)  1573140220897   0.135803
BinSearch_C: (1000000)  1573140220897   0.136248
BinSearch_D: (1000000)  1573140220897   0.170167
BinSearch_E: (1000000)  1573140220897   0.188973
BinSearch_A: (1000000)  1573140220897   0.084509
BinSearch_B: (1000000)  1573140220897   0.145219
BinSearch_C: (1000000)  1573140220897   0.129374
BinSearch_D: (1000000)  1573140220897   0.168213
BinSearch_E: (1000000)  1573140220897   0.186770
BinSearch_A: (1000000)  1573140220897   0.086911
BinSearch_B: (1000000)  1573140220897   0.141995
BinSearch_C: (1000000)  1573140220897   0.134353
BinSearch_D: (1000000)  1573140220897   0.169639
BinSearch_E: (1000000)  1573140220897   0.194442
BinSearch_A: (1000000)  1573140220897   0.082882
BinSearch_B: (1000000)  1573140220897   0.135095
BinSearch_C: (1000000)  1573140220897   0.129635
BinSearch_D: (1000000)  1573140220897   0.166059
BinSearch_E: (1000000)  1573140220897   0.186700
BinSearch_A: (1000000)  1573140220897   0.083190
BinSearch_B: (1000000)  1573140220897   0.134491
BinSearch_C: (1000000)  1573140220897   0.130103
BinSearch_D: (1000000)  1573140220897   0.169454
BinSearch_E: (1000000)  1573140220897   0.188583
BinSearch_A: (1000000)  1573140220897   0.083038
BinSearch_B: (1000000)  1573140220897   0.135738
BinSearch_C: (1000000)  1573140220897   0.129727
BinSearch_D: (1000000)  1573140220897   0.169101
BinSearch_E: (1000000)  1573140220897   0.188749
BinSearch_A: (1000000)  1573140220897   0.082099
BinSearch_B: (1000000)  1573140220897   0.135025
BinSearch_C: (1000000)  1573140220897   0.130743
BinSearch_D: (1000000)  1573140220897   0.168684
BinSearch_E: (1000000)  1573140220897   0.188640

原始数据统计

Program             Algorithm   Tests    Avg Time     Std Dev

binsearch-speed-1   BinSearch_A    10    0.0008451    0.0002014
                    BinSearch_B    10    0.0011357    0.0001442
                    BinSearch_C    10    0.0011160    0.0002801
                    BinSearch_D    10    0.0013983    0.0001003
                    BinSearch_E    10    0.0008039    0.0000366

binsearch-speed-2   BinSearch_A    10    0.0833624    0.0015203
                    BinSearch_B    10    0.1372278    0.0035168
                    BinSearch_C    10    0.1312759    0.0024403
                    BinSearch_D    10    0.1684263    0.0013514
                    BinSearch_E    10    0.1892315    0.0022148

暂定结论

当问题规模为一万时,“移位”算法 (BinSearch_E) 的性能似乎比简单的“中点”算法 (BinSearch_A) 好一点,但差异并不明显——我'尚未对数据进行 T 检验或类似检验。

当问题规模为 100 万时,'midpoint' 算法比 'shift' 算法好很多,实际上它的性能比三个更复杂的算法差。

这是出乎意料的(尤其是比更复杂的算法更糟糕);我希望这两种算法基本相同。我没有很好的解释为什么。这种结果说明了为什么基准测试很难。如果我不得不猜测,我会怀疑尽管更大的数组只需要大约 4 MiB 的内存,但“移位”算法的元素访问模式意味着缓存效率较低。证明(或反驳)那将是困难的——它需要一个比我更好的性能测试工程师。

鉴于算法 A(简单中点)的性能始终如一,它对所执行的测试基本上呈线性缩放,我会使用它而不是算法 E — 除非我能证明算法 E(移位)在代码正在使用的数据集。

这说明了如果性能最大化很重要,那么为什么基准测试很重要。

【讨论】:

  • 我很高兴你回答,但你没有拒绝再次说这段代码不起作用,即使我告诉你我的目标不是测试值是否在向量中.我的目标是看它是否更快地达到价值或价值不存在的速度更快地退出。但再次感谢您的宝贵时间
  • 不可能有意义地测试非工作代码——所以我必须有工作代码用于“移位”算法。而且我无法发明我所看到的性能特征;我很惊讶。
  • 你能告诉我你是怎么做到的吗?我可以在哪里学习你所做的,将来我可以自己测试性能?
  • 好吧,“其他 4”二分搜索算法是为我对SO 35147784 的回答而组装的。其中,A 和 B 相当简单地来自 Bentley 的Programming Pearls(我使用的是第一版而不是第二版,我也有)。对于 SO 问题,我将 C 派生为 B 的对应项。显然,使用 B 和 C 将给出 D 所需的输出,但我将两者组合成一个函数。 […续 1…]
  • […continuation 1…] 在从 B 发现的条目到end (或者相反,首先应用 C,然后在从 start 到 C 发现的条目的范围内应用 B)。但是,我还没有编写该代码或测量它。计时代码最初是在 1993 年写的,当时我想将 SQL 语句的计时添加到 SQL 命令解释器中。从那时起,我已经将它移植到了许多系统上。 […续 2…]
猜你喜欢
  • 2020-04-02
  • 2016-01-10
  • 1970-01-01
  • 2012-08-02
  • 1970-01-01
  • 2014-07-10
  • 2014-03-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多