算法 - 查找 - 二分查找 (Binary Search)

本文介绍如何使用C++实现通用的二分查找算法模板,适用于有序顺序表,详细阐述了整型查找、模板查找以及如何处理自定义类型。通过提供比较函数指针,实现了对不同数据类型的查找,简化了代码并提高了可复用性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

算法 - 查找 - 二分查找 (Binary Search)

返回分类:全部文章 >> 基础知识

返回上级:算法 - 查找与排序 (Searching and Sorting)

本文将用C++实现通用模板二分查找算法,复制代码直接可使用。

在查看本文之前,需要一些程序语言的基础。



1 二分查找简述 (Introduction)

二分查找,又叫折半查找,它只适用于有序顺序表。其时间复杂度 O(log2n) 。

假设表中有 n 个元素,查找过程为取区间中间元素的下标 mid ,对 mid 的关键字与给定值的关键字比较:

  • (1)如果与给定关键字相同,则查找成功,返回在表中的位置;

  • (2)如果给定关键字大,则更新左区间起始位置等于 mid + 1 ,即向右查找;

  • (3)如果给定关键字小,则更新右区间起始位置等于 mid - 1 ,即向左查找;

  • (4)重复过程,直到找到关键字(成功)或区间为空集(失败)。

通常情况下:

  • 返回值,代表下标;

  • 返回-1,代表没有找到关键字;

n = 2 h − 1 n=2^h-1 n=2h1 (h 为二叉树高)时,其判定树为满二叉树时,查找成功平均查找长度为:

A S L s u c c = 1 n [ ( n + 1 ) log ⁡ 2 ( n + 1 ) − n ] ≈ log ⁡ 2 ( n + 1 ) − 1 {ASL}_{succ} = \frac{1}{n} \left[ (n+1)\log_2 (n+1) -n \right] \approx \log_2 (n+1) - 1 ASLsucc=n1[(n+1)log2(n+1)n]log2(n+1)1

n &lt; 2 h − 1 n&lt;2^h-1 n<2h1

  • 其高度为

    h = ⌈ log ⁡ 2 ( n + 1 ) ⌉ h= \lceil \log_2(n+1) \rceil h=log2(n+1)

  • 查找成功平均查找长度为:

A S L s u c c = 1 n [ h × ( n + 1 ) + 1 − 2 h ] {ASL}_{succ} = \frac{1}{n} \left[ h \times (n+1) +1 - 2^h \right] ASLsucc=n1[h×(n+1)+12h]

举例,当元素集合E = { -3, 4, 8, 12, 18, 21 },查找元素 e = 12 时:

  • 已知:

    • 元素数量size = 6
    • 下标区间 [low, high] 为left = 0right = 5
  • 过程:

    ( 1 ) m i d 0 = ⌊ ( l e f t 0 + r i g h t 0 ) ÷ 2 ⌋ = 2 ∵ ( e = 12 ) &gt; ( E [ m i d 0 ] = 8 ) ∴ l e f t 1 = m i d 0 + 1 = 3 r i g h t 1 = r i g h t 0 ( 2 ) m i d 1 = ⌊ ( l e f t 1 + r i g h t 1 ) ÷ 2 ⌋ = 4 ∵ ( e = 12 ) = = ( E [ m i d 1 ] = 12 ) ∴ r e t u r n m i d 1 \begin{array}{rll} (1) &amp; mid_0 &amp;= \lfloor (left_0 + right_0) \div 2 \rfloor = 2 \\ &amp; \because &amp; (e=12) &gt; (E[mid_0]=8) \\ &amp; \therefore &amp; left_1 = mid_0 + 1 = 3 \\ &amp; &amp; right_1 = right_0 \\ (2) &amp; mid_1 &amp;= \lfloor (left_1 + right_1) \div 2 \rfloor = 4 \\ &amp; \because &amp; (e=12) == (E[mid_1]=12) \\ &amp; \therefore &amp; return \quad mid_1 \\ \end{array} (1)(2)mid0mid1=(left0+right0)÷2=2(e=12)>(E[mid0]=8)left1=mid0+1=3right1=right0=(left1+right1)÷2=4(e=12)==(E[mid1]=12)returnmid1

  • 其判定树:

    <
    >
    <
    >
    <
    >
    -3
    4
    E[mid]=8
    12
    18
    21
    NULL

之后的程序,我们以数组列表形式描述。

注意:代码全部使用std::vector<MyType>作为数组列表,如果你用指针数组MyType*,还需传入数组大小size


2 整型查找 (Interger Search)

一般举例中,查找最基本的元素本身就是整型。

// Author: https://blog.youkuaiyun.com/DarkRabbit
// Binary Search
// 整型有序表 - 二分(折半)查找
// params:
//      list:       查找的有序表
//      element:    查找的元素
// return:
//      int:        找到的下标,-1为表中没有
int BinarySearch(const std::vector<int>& list,
                 const int& element)
{
    int left = 0;
    int right = list.size() - 1;
    int mid;
    while (left <= right)
    {
        mid = (left + right) / 2; // 中值

        if (element > list[mid]) // x > mid ,向右查找
        {
            left = mid + 1;
        }
        else if (element < list[mid]) // x < mid ,向左查找
        {
            right = mid - 1;
        }
        else
        {
            return mid;
        }
    }

    return -1;
}

3 模板查找 (Template Search)

在实际应用中,通常情况下,列表存储的都是一些数据(结构体或类),它们都包含唯一标识(即关键字Key)。

我们一般不会将它们的关键字重新建立一个列表,再去查找。

这在C++中通常用模板 (template) 来解决,其它语言多数用泛型 (Genericity) 来解决。

我们的要求仅仅是用结构中的关键字进行比较,即我们只关心关键字而不关心这个数据的类型。这样使用自定义类型也不怕了,所以可以使用一个函数指针传入比较方法,在函数中自定义比较。

我们规定此函数指针的结果:

  • 返回值大于0,则给定关键字大;

  • 返回值等于0,成功找到,则返回结构;

  • 返回值小于0,则给定关键字小。

我们可以限定给定区间,不一定扫描整个表,这样在其它算法中可以有效调用它。

我们接下来改造成模板函数:

// Author: https://blog.youkuaiyun.com/DarkRabbit
// Binary Search

// 模板有序表 - 二分(折半)查找
// params:
//      list:       查找的有序表
//      element:    查找的元素
//      left:       开始查找下标
//      right:      结束查找下标
//      pEqual:     判断查找标准,
//                  >0 查找的值比中值大
//                  =0 找到了
//                  <0 查找的值比中值小
// return:
//      int:        找到的下标,-1为表中没有
template<typename T>
int BinarySearch(const std::vector<T>& list,
                 const T& element,
                 int left,
                 int right,
                 int (*pEqual)(const T&, const T&))
{
    if (pEqual == nullptr)
    {
        return -1;
    }

    if (left > right) // 交换
    {
        int tmp = left;
        left = right;
        right = tmp;
    }

    if (left < 0)
    {
        left = 0;
    }

    if (right >= list.size())
    {
        right = list.size() - 1;
    }

    int isEqual;
    int mid;
    while (left <= right)
    {
        mid = (left + right) / 2; // 中值
        isEqual = (*pEqual)(element, list[mid]); // 比较查找值与中值
        
        if (isEqual > 0) // x > mid ,向右查找
        {
            left = mid + 1;
        }
        else if (isEqual < 0) // x < mid ,向左查找
        {
            right = mid - 1;
        }
        else
        {
            return mid;
        }
    }

    return -1;
}

// 模板有序表 - 二分(折半)查找
// params:
//      list:       查找的有序表
//      element:    查找的元素
//      pEqual:     判断查找标准,
//                  >0 查找的值比中值大
//                  =0 找到了
//                  <0 查找的值比中值小
// return:
//      int:        找到的下标,-1为表中没有
template<typename T>
int BinarySearch(const std::vector<T>& list,
                 const T& element,
                 int (*pEqual)(const T&, const T&))
{
    return BinarySearch(list, element, 0, list.size() - 1, pEqual);
}

4 修改整型查找 (Modify Interger Search)

有了模板函数后,我们之前的整型函数可以进行修改,直接调用模板函数即可。

我们在这里直接传入 Lambda 表达式:

// Author: https://blog.youkuaiyun.com/DarkRabbit
// Binary Search

// 整型有序表 - 二分(折半)查找
// params:
//      list:       查找的有序表
//      element:    查找的元素
//      left:       开始查找下标
//      right:      结束查找下标
// return:
//      int:        找到的下标,-1为表中没有
int BinarySearch(const std::vector<int>& list,
                 const int& element,
                 int left,
                 int right)
{
    return BinarySearch<int>(list, element, left, right, 
                             [](const int& x, const int& y)->int
    {
        return x - y;
    });
}

// 整型有序表 - 二分(折半)查找
// params:
//      list:       查找的有序表
//      element:    查找的元素
// return:
//      int:        找到的下标,-1为表中没有
int BinarySearch(const std::vector<int>& list,
                 const int& element)
{
    return BinarySearch<int>(list, element, 0, list.size() - 1, 
                             [](const int& x, const int& y)->int
    {
        return x - y;
    });
}

5 自定义类型的调用 (Custom Type)

类似的,自定义类型调用:

// Author: https://blog.youkuaiyun.com/DarkRabbit
// Binary Search

#include <vector>
#include <string>
#include <iostream>
using namespace std;

struct MyElement
{
    int key;
    string data;
};

int MyBinaryCompare(const MyElement& x, 
                    const MyElement& y)
{
    return x.key - y.key;
}

int main()
{
    vector<MyElement> list; // 列表
    MyElement tofind; // 需要查找的元素

    // TODO 省略初始化列表和元素的过程

    int index = BinarySearch<MyElement>(list, tofind, 
                [](const MyElement& x, const MyElement& y)->int
                {
                    return x.key - y.key;
                });

    // 以上调用方法等同于
    // int index = BinarySearch<MyElement>(list, tofind, MyBinaryCompare);

    if (index != -1)
    {
        // do something
        cout << "找到了下标:" << index << endl;
    }

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值