二分查找

二分查找

二分查找是一个 搜索算法 ,我们需要准备一个 已排序数组 以及一个待查找的元素
通过该算法, 返回该元素的 index ,若该元素不存在于数组中, 则返回 -1

代码详解

算法准备:\
准备一个 _ 已排序数组 _ (arr) 以及一个待查找 _ 元素 _ (x)

算法思路:\

  • 计算数组的中间索引,从而将数组区分为左右两个部分
    • 若 数组长度为 arr.len = 10 ,则中间索引为 mid_index = 5 ,那么将数组区分为 arr[0..4] , 与 arr[6..9] 两个部分
  • 比较 xarr[mid_index] 的大小
    • if x = arr[mid_index] 那么找出的索引为 5
    • if x < arr[mid_index] 那么要找的索引在 arr[0..4] 部分
    • if x > arr[mid_index] 那么要找的索引在 arr[6..9] 部分
  • 重复以上步骤,直到
    • 找到元素索引
    • min_index > max_index 元素不存在于数组中

代码实现

  • 伪代码
function binary_search(value,arr){
  min_index = 0;
  max_index = arr.len()
  while min_index <= max_index{
    mid_index = (min_index + max_index) / 2
    if arr[mid_index] == value{
      return mid_index
    }
    elseif value > arr[mid_index] {
      min_index = mid_index + 1
    }
    else {
      max_index = mid_index - 1
    }
  }
  return -1;
}
  • rust 实现
fn binary_search<T: PartialOrd>(arr: &[T], target: &T) -> Option<usize> {
    let mut min_index = 0;
    let mut max_index = arr.len() -1;
    while min_index <= max_index {
        let mid_index = (min_index + max_index) / 2;
        if arr[mid_index] > *target {
            max_index = mid_index - 1;
        }
        else if arr[mid_index] < *target {
            min_index = mid_index + 1;
        } else {
            return Some(mid_index);
        }
    }
    None
}

算法分析

最优的算法复杂度应该是 $O(1)$
平均算法复杂度应该是

他一直是 /2 /2 /2

所以说这个东西是如何索引的

二分查找的变种

以上的算法对只有一个相同的数值的时候是有效的,但如果存在多个相同的数组呢?如 [1,1,2,2,3,3,3,3,5,5]

针对上述情况,我们可以提出很多问题,如:

  1. 查找是否存在数组中元素是否存在
  2. 查找第一个等于给定值的元素
  3. 查找最后一个等于给定值的元素
  4. 查找第一个大于给定值的元素
  5. 查找最后一个小于给定值的元素

面对上述问题,上文给出的算法就不太行了,所以我们需要针对算法进行改进

// 查找数组(arr)中是否存在元素(target)
fn contains(arr,target){
  let min_index = 0;
  let max_index = arr.len() - 1;
  while min_index <= max_index {
    let mid_index = (min_index + max_index) / 2;
    if arr[mid_index] > target {
      max_index = mid_index - 1;
    }
    else if arr[mid_index] < target {
      min_index = mid_index + 1;
    } else {
      return true
    }
  }
  return false
}
// 查找第一个等于给定值的元素
fn first(arr,target){
  // 当返回值为 -1 的时候, 表明并没有找到该元素
  let ans = -1;
  let min_index = 0;
  let max_index = arr.len() - 1;
  while min_index <= max_index {
    let mid_index = (min_index + max_index) / 2;
    if arr[mid_index] > target {
      // 如果 arr[mid_index] > target 那么就说明我们要找的元素在 [min_index,mid_index-1] 中
      // 下文同理
      max_index = mid_index - 1;
    }else if arr[mid_index] < target{
      min_index = mid_index + 1;
    }else{
      // 因为要找第一个值,所以要找的范围在 [min_index,mid_index] 中
      //
      ans = mid_index
      max_index = mid_index - 1;
    }
  }
  return ans
}
// 变种三和变种二思路相似,这里就不再赘述了
...

// 查找第一个大于给定值的元素
fn leastgreater(arr,target){
  let ans = -1;
  let min_index = 0;
  let max_index = arr.len() - 1;
  while min_index <= max_index {
    let mid_index = (min_index + max_index) / 2;
    if arr[mid_index] < target{
      min_index = mid_index + 1;
    }else if arr[mid_index] > target{
      ans = mid_index;
      max_index = mid_index - 1;
    }else{
      min_index = mid_index + 1;
    }
  }
  return ans
}

单边二分查找解析

二分查找的另外一个变种就是 单边二分查找 也叫 元二分查找

与 二分查找不同的是,

使用场景

单边二分查找 只在一侧进行查找,通常用于特定的应用场景。

比如说大量的数据需要快速查找的情况下,或者当 比较 操作花费比较高的情况下

算法模型

单边二分查找并不循环比较大小,而是通过 一定的间隔 进行查找,通常设定为 2

算法步骤

首先我们设定间隔为 2,也就是通过每次跳过 2 个元素来进行查找,直到找到目标元素或超出数组范围。

因为设定间隔为2,所以我们确定通过二进制来表示数组的索引

比如说有数组 arr [-2,-1,0,1,2,3,4,5] ,使用二进制索引,那么 3 的索引为 101,即 5arr[5]

所以在数据 arr 中,其计算方法为 $log_2(n) = bits$,其中 n为数组长度 \
所以在这里用 3bits 来表示数据的索引

也就是说 arr[0] 的索引为 000arr[1] 的索引为 001arr[2] 的索引为 010,依次类推

所以如果要找 'valueToFind' 的索引,我们要做的事情就是确认索引的位置,假设 valueToFind=3, 那么我们需要找到索引 101 , 也就是 arr[5]

根据 arr 的长度,我们可以将索引分为 3 个部分,分别为 $x_1 x_2 x_3$,其中 $x_1$ 表示最高位,$x_2$ 表示中间位,$x_3$ 表示最低位

所以我们的问题是 要在 arr 中找到 valueToFind 的索引 根据上面的例子,我们给出具体的步骤

  1. 已知索引为 3bits 我们假定为 $x_1 x_2 x_3$(详情见上),我们要做的工作就是
    • 确认 $x_1$ 的值
    • 确认 $x_2$ 的值
    • 确认 $x_3$ 的值
      从而确认索引的值
  2. 确认 $x_1$ 的值
    • 如果 $x_1 = 0$ 那么我们要找的索引在 arr[0..3]
    • 如果 $x_1 = 1$ 那么我们要找的索引在 arr[4..7]
    • 我们一般假设 $x_1 = 1$ ,也就是从 arr[4..7] 中查找
    • 已知 $x_1 = 1$ ,那么索引的值最小为 100, 最大为 111 \
      我们先从最小的值开始查找,又已知 valueToFind = 3valueToFind > arr[100](arr[4]) \
      所以我们需要向后查找,也就是我们要找的范围为 arr[100..111] 据此我们可以确认 $x_1 = 1$
  3. 确认 $x_2$ 的值
    • 如果 $x_2 = 0$ 那么我们要找的索引在 arr[100..101]
    • 如果 $x_2 = 1$ 那么我们要找的索引在 arr[110..111]
    • 我们一般假设 $x_2 = 1$ ,也就是从 arr[110..111] 中查找
    • 已知 $x_2$ = 1$ ,那么索引的值最小为 110, 最大为 111 \
      我们先从最小的值开始查找,又已知 valueToFind = 3valueToFind < arr[110](arr[6]) \
      所以我们需要向前查找,也就是我们要找的范围为 arr[100..101] 据此我们可以确认 $x_2 = 0$
  4. 确认 $x_3$ 的值
    • 如果 $x_3 = 0$ 那么我们要找的索引在 arr[100]
    • 如果 $x_3 = 1$ 那么我们要找的索引在 arr[101]
    • 我们一般假设 $x_3 = 1$ ,也就是从 arr[101] 中查找
    • 已知 $x_3 = 1$ ,那么索引的值最小为 101, 最大为 101 \
      我们先从最小的值开始查找,又已知 valueToFind = 3valueToFind = arr[101](arr[5]) \
      所以我们找到了目标索引 101 据此我们可以确认 $x_3 = 1$

算法实现

fn meta_binary_search<T: PartialOrd>(arr: &[T], target: &T) -> Option<usize> {
    let n = arr.len();
    // 当为空数组的时候返回 _None_
    if n == 0 {
        return None;
    }

    // 确认数组 _bits_ 数
    let mut num_bits_for_maxindex = (n - 1).ilog2() + 1;
    let mut index = 0;

    // 确认边界值
    if index < n && arr[index] == *target {
        return Some(index);
    }
    while num_bits_for_maxindex > 0 {
        // 按位或, 确保在不影响其他 _bits_ 的情况下更改对应位数的值
        let new_index = index | 1 << (num_bits_for_maxindex - 1);
        // 如果找到了就返回
        if new_index < n && arr[new_index] == *target {
            return Some(new_index);
        } else if arr[new_index] < *target {
            index = new_index
        }
        num_bits_for_maxindex -= 1;
    }
    None
}
posted @ 2025-07-25 11:43  五花肉炒河粉  阅读(1)  评论(0)    收藏  举报