经典算法之二分查找及其变体

前言

在有序数组中查找元素的时候,如果是从头到尾遍历一遍查找,它的时间复杂度是O(N),而通过二分查找,每次查找过滤一半元素,可以优化到O(logn)。

如图所示,low 和 high表示待查找区间的下标,mid 表示待查找区间的中间元素下标,通过控制low、high的位置来缩小查找区间的范围。
经典算法之二分查找及其变体_第1张图片

1、二分查找

思路

1、mid是low、high的中间节点,每次循环与target目标值对比大小
2、如果mid大于target,则low移动到mid+1的位置
如果mid小于target,则high移动到mid-1的位置
如果mid等于target则说明找到指定的元素,返回结果
3、如果low、high重合了还没找到指定元素,则说明该元素不存在

注意:

1、终止条件是min <= max,此时min、max重合,查找区间为0
2、mid的取值,用mid=(low+high)/2的话,如果值特别大是可能会溢出的,所以用mid= min + (max-min)/2

func BinarySearch(a []int, v int) int {
    length := len(a)
    min := 0
    max := length - 1
    for min <= max {
        mid := min + (max-min)/2
        if a[mid] == v {
            return mid
        } else if a[mid] < v {
            min = mid + 1
        } else if a[mid] > v {
            max = mid - 1
        }
    }
    return -1
}

变体1:查找第一个值等于给定值的元素

思路

查第一个出现的目标值,就是在查到目标值以后,修改max的位置继续往左查找,直到mid的位置为0了或者mid的左边位置元素不等于目标值了停止。

func BinarySearchFirst(a []int, v int) int {
    length := len(a)
    min := 0
    max := length - 1
    for min <= max {
        mid := min + (max-min)/2
        if a[mid] == v {
            //如果相邻左边一位不等于搜索值或者搜索值为数组第一个元素则返回
            if (mid == 0) || (a[mid-1] != v) {
                return mid
            } else {
                max = mid - 1
            }
        } else if a[mid] < v {
            min = mid + 1
        } else if a[mid] > v {
            max = mid - 1
        }
    }
    return -1
}

变体2:查找最后一个值等于给定值的元素

思路

查最后一个出现的目标值,就是在查到目标值以后,修改min的位置继续往右查找,直到mid的位置为length-1了或者mid的右边位置元素不等于目标值了停止。

func BinarySearchLast(a []int, v int) int {
    length := len(a)
    min := 0
    max := length - 1
    for min <= max {
        mid := min + (max-min)/2
        if a[mid] == v {
            //如果相邻右边一位不等于搜索值或者搜索值为数组最后一个元素则返回
            if (mid == length-1) || (a[mid+1] != v) {
                return mid
            } else {
                min = mid + 1
            }
        } else if a[mid] < v {
            min = mid + 1
        } else if a[mid] > v {
            max = mid - 1
        }
    }
    return -1
}

变体3:查找第一个值大于等于给定值的元素

思路

1、如果a[mid] 的值小于target,则min = mid + 1
2、如果a[mid] 的值大于等于target,则需判断这个a[mid] 是不是我们要找的第一个值大于等于给定值的元素。如果 a[mid] 左边已经没有元素,或者左边的元素小于要查找的值target,那 a[mid] 就是我们要找的元素。否则将max更新为mid-1继续往左查找。

func BinarySearchFirstGT(a []int, v int) int {
    length := len(a)
    min := 0
    max := length - 1
    for min <= max {
        mid := min + (max-min)/2
        if a[mid] >= v {
            //如果相邻左边一位小于搜索值或者搜索值为数组第一个元素则返回
            if (mid == 0) || (a[mid-1] < v) {
                return mid
            } else {
                max = mid - 1
            }
        } else {
            min = mid + 1
        }
    }
    return -1
}

变体4:查找最后一个小于等于给定值的元素

思路

1、如果a[mid]的值大于target,则max = mid - 1
2、如果a[mid]的值小于等于target,则需判断这个a[mid] 是不是我们要找的最后一个值小于等于给定值的元素。如果 a[mid] 右边已经没有元素,或者右边的元素小于要查找的值target,那 a[mid] 就是我们要找的元素。否则将min更新为mid+1继续往右查找。

func BinarySearchLastLT(a []int, v int) int {
    length := len(a)
    min := 0
    max := length - 1
    for min <= max {
        mid := min + (max-min)/2
        if a[mid] <= v {
            //如果相邻右边一位大于搜索值或者搜索值为数组最后一个元素则返回
            if (mid == length-1) || (a[mid+1] > v) {
                return mid
            } else {
                min = mid + 1
            }
        } else if a[mid] > v {
            max = mid - 1
        }
    }
    return -1
}
参考资料:《数据结构与算法之美》

你可能感兴趣的:(go算法二分查找)