二进制搜索位操作

时间:2016-04-22 21:12:55

标签: binary-search

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

val decimal = "(\\d+)".r

line match {
  case Seq("Data1Name", decimal(_), "desc1", "..")  =>
    processData1(...)
  case Seq("Data2Name", decimal(_), "desc2", "..")  =>
    processData2(...)
  case Seq("Data3Name", decimal(_), "desc3", "...") =>
    processData3(...)
  case Seq("Data4Name", decimal(_), "desc4", "...") => 
    processData4(...)
  case Seq("Data5Name", decimal(_), "desc5", "...", decimal(_), '...', ...) => 
    processData5(...)
  case _ => Some(Error(s"..."))
} 

以及您找到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的值和您的值应用于数组左侧或右侧?

1 个答案:

答案 0 :(得分: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 - 找到与T匹配的数组X [0..N-1]中的最小索引P.
  • BinSearch_C - 找到与T匹配的数组X [0..N-1]中的最大索引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

暂定结论

当问题大小为一万时,那么'shift'算法(BinSearch_E)似乎比简单的'中点'算法(BinSearch_A)表现得更好,但差异不大显然很重要 - 我没有在数据上运行T-Test或类似的。

当问题规模为100万时,“中点”算法比“移位”算法要好很多,实际上它比三个更复杂的算法表现更差。

这是出乎意料的(特别是比更复杂的算法更糟糕);我预计这两种算法基本相同。我没有很好的解释原因。这种结果表明为什么基准测试很难。如果我不得不猜测,我怀疑尽管大型阵列只需要大约4 MiB的内存,但'shift'算法的元素上的访问模式意味着缓存效率较低。证明(或反驳)很难 - 这需要一个比我更好的性能测试工程师。

考虑到算法A(简单中点)的一致性能,它对于所执行的测试基本上是线性缩放的,我会使用它而不是算法E - 除非我能证明算法E(移位)在大小上给出了更好的性能代码正在使用的数据集。

这表明,如果最高性能非常重要,那么基准测试的重要性。