二分查找

260 阅读3分钟

二分查找

核心思想:二分查找针对的是一个有序的数据集合,查找思想有点类似分治思想。每次都通过跟区间的中间元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为 0。

写二分查找的三个条件

  • 循环退出条件 (注意是 low<=high,而不是 low)
  • mid 的取值

实际上,mid=(low+high)/2 这种写法是有问题的。因为如果 low 和 high 比较大的话,两者之和就有可能会溢出。改进的方法是将 mid 的计算方式写成 low+(high-low)/2。更进一步,如果要将性能优化到极致的话,我们可以将这里的除以 2 操作转化成位运算 low+((high-low)>>1)。因为相比除法运算来说,计算机处理位运算要快得多。

  • low 和 high 的更新【low=mid+1,high=mid-1。注意这里的 +1 和 -1,如果直接写成 low=mid 或者 high=mid,就可能会发生死循环。比如,当 high=3,low=3 时,如果 a[3]不等于 value,就会导致一直循环不退出。) 一般情况代码实现】
迭代:
func find(a []int, value int) int {
	aLen := len(a)
	low := 0
	high := aLen - 1
	for low <= high {
		mid := low + (high - low) / 2
		if a[mid] == value {
			return mid
		} else if a[mid] > value {
			high = mid - 1

		} else {
			low = mid + 1
		}
	}
	return  -1
}
递归:
func recursionFind(a []int, low, high, value int) int  {
	if low > high {
		return -1
	}
	mid := low + (high - low) / 2
	if a[mid] == value {
		return  mid
	} else if a[mid] > value {
		return recursionFind(a, low, mid-1, value )
	} else {
		return recursionFind(a, mid+1, high, value)
	}
}

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

代码:

func binarySearch(a []int, value int) int {
	aLen := len(a)
	low := 0
	high := aLen-1
	for low <= high {
		mid := low + (high - low) / 2
		if a[mid] < value {
			low = mid + 1
		} else if a[mid] > value {
			high = mid - 1
		} else { // value == mid
			//如果mid等于0,那么这个元素是数组的第一个元素,肯定是我们要找的
			// 如果 mid 不等于 0,但 a[mid]的前一个元素 a[mid-1]不等于 value,那也说明 a[mid]就是我们要找的第一个值等于给定值的元素
			if mid == 0 || a[mid-1] != value {
				return mid
			} else {
				//如果经过检查之后,a[mid-1]也等于value, 说明此元素必然存在【low, mid-1】区间内
				high = mid - 1
			}
		}
	}
	return -1
}
变体二:查找最后一个值等于给定值的元素

代码:

func binarySearch(a []int, value int) int {
    aLen := len(a)
    low := 0
    high := aLen-1
    for low <= high {
        mid := low + (high - low) / 2
        if a[mid] < value {
            low = mid + 1        
        } else if a[mid] > value {
            high = mid - 1
        } else {    
            if mid == aLen-1 || a[mid+1] != value {
                return mid
            } else {
                low = mid + 1    
            }
            
        }
    }
    return -1  
}
变体三:查找第一个大于等于给定值的元素

代码:

func binarySearch(a []int, value int) int {
    aLen := len(a)
    low := 0
    high := aLen-1
    for low <= high {
        mid := low + (high - low) / 2
        if a[mid] >= value {
            //mid ==0说明是第一个元素, 返回
            //a[mid-1] < value 说明 mid 是第一个大于或等于的元素, 返回
            if mid ==0 || a[mid-1] < value {
                return mid
            } else {
                //否则,查找区间在【low,mid-1】
                high = mid - 1  
            }
        } else {
            low = mid + 1
        }
    }
    return -1  
}

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

代码:

func binarySearch(a []int, value int) int {
    aLen := len(a)
    low := 0
    high := aLen-1
    for low <= high {
        mid := low + (high - low) / 2
        if a[mid] <= value {
            //如果mid是最后一个元素,则直接返回
            //如果a[mid+1] > value 说明mid 是最后一个大于等于value的元素
            if mid == aLen-1 || a[mid+1] > value {
                return mid 
            } else {
                //否则查找区间在【mid+1,high】之间
                low = mid + 1
            }
        } else {
            high = mid - 1 
        }
    }
    return -1  
}