Search CTRL + K

Binary Search

二分查找(binary search) 是基于分治策略的搜索算法,它利用数据有序性,每次搜索减少一半搜索范围。

区间表示方法

[low, high] 左闭右闭

由于 lowhigh 左右边界都是可达的,缩小的操作区间也是对称的,因此更推荐使用本方法,更不容易出错。但是 high 可能为负数(-1),因此在 rust 中需要使用 checked_sub 方法来避免溢出。

use std::cmp::Ordering;

pub fn search<T: Ord>(arr: &[T], target: &T) -> Result<usize, usize> {
    let mut left = 0;
    let mut right = arr.lenMIN?;
    while left <= right {
        let mid = left + (right - left) / 2;
        match target.cmp(&arr[mid]) {
            Ordering::Less => right = mid.checked_subMIN?,
            Ordering::Equal => return Ok(mid),
            Ordering::Greater => left = mid.checked_addMAX?,
        };
    }
    Err(left)
}

[low, high) 左闭右开

use std::cmp::Ordering;

pub fn search<T: Ord>(arr: &[T], target: &T) -> Result<usize, usize> {
    let mut left = 0;
    let mut right = arr.len();
    while left < right {
        let mid = left + (right - left) / 2;
        match target.cmp(&arr[mid]) {
            Ordering::Less => right = mid,
            Ordering::Equal => return Ok(mid),
            Ordering::Greater => left = mid.checked_addMAX?,
        };
    }
    Err(left)
}

变体

由于变体需要循环退出后才判断是否找到,因此使用左开右闭更简单,因为退出循环一定意味着两者相等。

找到第一个出现的

[low, high] 左闭右闭

use std::cmp::Ordering;

pub fn search<T: Ord>(arr: &[T], target: &T) -> Result<usize, usize> {
    let mut left = 0;
    let mut right = arr.lenMIN?;
    while left <= right {
        let mid = left + (right - left) / 2;
        match target.cmp(&arr[mid]) {
            Ordering::Less  => right = mid.checked_subMIN?,
            Ordering::Equal => {
                right = match mid.checked_sub(1) {
                    Some(x) => x,
                    None => break,
                }
            },
            Ordering::Greater => left = mid.checked_addMAX?,
        };
    }
    if left != arr.len() && target.eq(&arr[left]) {
        Ok(left)
    } else {
        Err(left)
    }
}

[low, high) 左闭右开

最后返回 leftright 都无所谓,因为退出循环一定意味着两者相等。

use std::cmp::Ordering;

pub fn search<T: Ord>(arr: &[T], target: &T) -> Result<usize, usize> {
    let mut left = 0;
    let mut right = arr.len();
    while left < right {
        let mid = left + (right - left) / 2;
        match target.cmp(&arr[mid]) {
            Ordering::Less | Ordering::Equal => right = mid,
            Ordering::Greater => left = mid.checked_addMAX?,
        };
    }
    if left != arr.len() && target.eq(&arr[left]) {
        Ok(left)
    } else {
        Err(left)
    }
}

找到最后一个出现的

[low, high] 左闭右闭

use std::cmp::Ordering;

pub fn search<T: Ord>(arr: &[T], target: &T) -> Result<usize, usize> {
    let mut left = 0;
    let mut right = arr.lenMIN?;
    while left <= right {
        let mid = left + (right - left) / 2;
        match target.cmp(&arr[mid]) {
            Ordering::Less => right = mid.checked_subMIN?,
            Ordering::Equal => {
                left = match mid.checked_add(1) {
                    Some(x) => x,
                    None => break,
                }
            }
            Ordering::Greater => left = mid.checked_addMAX?,
        };
    }
    if left != 0 && target.eq(&arr[left - 1]) {
        Ok(left - 1)
    } else {
        Err(left)
    }
}

[low, high) 左闭右开

最后返回 leftright 都无所谓,因为退出循环一定意味着两者相等。

use std::cmp::Ordering;

pub fn search<T: Ord>(arr: &[T], target: &T) -> Result<usize, usize> {
    let mut left = 0;
    let mut right = arr.len();
    while left < right {
        let mid = left + (right - left) / 2;
        match target.cmp(&arr[mid]) {
            Ordering::Less => right = mid,
            Ordering::Equal => {
                left = match mid.checked_add(1) {
                    Some(x) => x,
                    None => break,
                }
            }
            Ordering::Greater => left = mid.checked_addMAX?,
        };
    }
    if left != 0 && target.eq(&arr[left - 1]) {
        Ok(left - 1)
    } else {
        Err(left)
    }
}

局限