数据结构和算法-二分查找

152 阅读2分钟

定义

在一个有序数组中,以O(logn)的时间复杂度找到指定的元素的算法,称之为二分查找。

标准实现

当数组中的元素为严格递增时,标准实现如下

func binarySearch(nums []int, target int) int {
    low, high := 0, len(nums)-1
    for low <= high {
        mid := low + ((high-low)>>1)
        if nums[mid] == target {
            return mid
        } else if nums[mid] < target {
            low = mid + 1
        } else {
            high = mid - 1
        }
    }
    return -1
}
  • 结束条件:low > high
  • mid=(mid+high)/2 为了避免加法溢出,优化为 mid=low+(high-low)/2;为了进一步提高性能,可以继续优化为mid=low+((high-low)>>1)
  • 查找不到元素时返回-1

变体

当数组不是严格递增(或者递减)的时候,即存在相同的元素,会有以下4种常见的查询需求

找到第一个等于target的元素

func binarySearch(nums []int, target int) int {
    low, high := 0, len(nums)-1
    for low <= high {
        mid := low + ((high-low)>>1)
        if nums[mid] == target {
            if mid == 0 || nums[mid-1] != target {
                return mid
            }
            high = mid - 1
        } else if nums[mid] < target {
            low = mid + 1
        } else {
            high = mid - 1
        }
    }
    return -1
}

找到最后一个等于target的元素

func binarySearch(nums []int, target int) int {
    low, high := 0, len(nums)-1
    for low <= high {
        mid := low + ((high-low)>>1)
        if nums[mid] == target {
            if mid == len(nums)-1 || nums[mid+1] != target {
                return mid
            }
            low = mid + 1
        } else if nums[mid] < target {
            low = mid + 1
        } else {
            high = mid - 1
        }
    }
    return -1
}

找到第一个大于等于target的元素

func binarySearch(nums []int, target int) int {
    low, high := 0, len(nums)-1
    for low <= high {
        mid := low + ((high-low)>>1)
        if nums[mid] >= target {
            if mid == 0 || nums[mid-1] < target {
                return mid
            }
            high = mid - 1
        } else {
            low = mid + 1
        }
    }
    return -1
}

找到最后一个小于等于target的元素

func binarySearch(nums []int, target int) int {
    low, high := 0, len(nums)-1
    for low <= high {
        mid := low + ((high-low)>>1)
        if nums[mid] <= target {
            if mid == len(nums)-1 || nums[mid+1] > target {
                return mid
            }
            low = mid + 1
        } else {
            high = mid -1
        }
    }
    return -1
}

拓展问题

二分查找可以用递归实现吗?

可以,以标准实现为例

  1. 递归公式
bs(target, low, high) = mid || bs(target, low, mid-1) || bs(target, mid+1, high)
  1. 结束条件
low > high
  1. 翻译成代码
func binarySearch(nums []int, target int) int {
    low, high := 0, len(nums)-1
    return bs(nums, target, low, high)
}

func bs(nums []int, target, low, high int) int {
    if low > high {
        return -1
    }
    mid := low + ((high-low)>>1)
    if nums[mid] == target {
        reutrn mid
    } else if nums[min] < target {
        return bs(nums, target, mid+1, high)
    } else {
        return bs(nums, target, low, mid-1)
    }
}

链表是否也能使用二分查找?

链表不支持随机访问,即使链表数据是有序的,也不能使用二分查找,只能从头到尾遍历,时间复杂度为O(n)。

但是我们可以对链表进行索引改造,使其查询效率达到O(logn),这种数据结构称为跳表。不过有得有失,虽然查询效率上来了,但是为了维护索引平衡性,插入删除元素的时间复杂度会从O(1)下降到O(logn),同时也需要额外的空间来存储索引。