每日一算法——二分查找

36 阅读3分钟

算法概述

二分查找是一种在有序数组中查找特定元素的搜索算法。他的核心思想是通过不断将搜索范围减半来快速定位目标元素,通过先查询到中间元素,再根据目标字的位置决定继续向左还是向右查找。与线性查找(行第一个元素开始逐个比对)相比,二分查找的效率会高很多。

核心思路和原理

二分查找的核心思想是 分而治之。算法的工作原理如下:

  1. 确定搜索范围: 初始化左边界 left 为 0,右边界 right 为数组长度 -1;
  2. 计算中间位置: mid = (left + right) // 2
  3. 比较中间元素:
    • 如果 arr[mid] 等于目标值, 返回 mid;
    • 如果 arr[mid] 小于目标值, 说明目标在右半部分, 将 left 设置为 mid+1;
    • 如果 arr[mid] 大于目标值, 说明目标在左半部分, 将 right 设置为 mid-1;
  4. 重复步骤 2-3: 直至找到目标或者搜索范围为空。

关键概念:

  1. 有序性要求: 二分查找要求数组必须是有序的,这是算法能够工作的前提;
  2. 边界条件: 循环条件是 left <= right, 当 left > right 时说明搜索范围为空;
  3. 中间位置计算: 使 (left + right) // 2 可以避免整数溢出问题.

算法流程图

流程图说明:

  1. 算法从检查搜索范围是否有效开始(left <= right)
  2. 每次循环计算中间位置 mid,比较中间元素与目标值
  3. 根据比较结果调整搜索范围(左半部分或右半部分)
  4. 如果找到目标值立即返回,否则继续缩小搜索范围
  5. 当搜索范围为空时,说明目标不存在,返回 -1

详细示例演示

假设我们要在有序数组  [2, 5, 8, 12, 16, 23, 38, 56, 72, 91]  中查找目标值  23 。

  1. 第 1 轮查找:

    • left = 0, right = 9
    • mid = (0 + 9) // 2 = 4
    • arr[4] = 16 < 23,说明目标在右半部分
    • 调整:left = mid + 1 = 5
  2. 第 2 轮查找:

    • left = 5, right = 9
    • mid = (5 + 9) // 2 = 7
    • arr[7] = 56 > 23,说明目标在左半部分
    • 调整:right = mid - 1 = 6
  3. 第 3 轮查找:

    • left = 5, right = 6
    • mid = (5 + 6) // 2 = 5
    • arr[5] = 23 == 23,找到目标!
    • 返回索引 5
  4. 查找过程总结:

    • 初始范围:0-9,中间元素 16
    • 调整后范围:5-9,中间元素 56
    • 调整后范围:5-6,中间元素 23(找到)

总共比较 3 次,而线性查找需要 6 次

Python 代码实现

def binary_search(arr, target):
    left, right = 0, len(arr) - 1
    while left <= right:
        mid = (left + right) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
    return -1  # 未找到目标元素

if __name__ == '__main__':
    arr = [2, 5, 8, 12, 16, 23, 38, 56, 72, 91]
    target = 23

    index = binary_search(arr, target)
    if index != -1:
        print(f"目标元素 {target} 找到,索引为 {index}")
    else:
        print(f"目标元素 {target} 未找到")

代码说明:

  1. 使用 while 循环控制搜索过程,条件是 left <= right
  2. 每次循环计算中间位置 mid,避免使用 (left + right) // 2 可能导致的整数溢出
  3. 通过比较中间元素与目标值,不断缩小搜索范围
  4. 如果找到目标立即返回索引,否则循环结束后返回 -1