go 实现二分查找法

545 阅读3分钟

二分查找法是最为基础的查找方式主要应用于数据的查找为主,而数据的特点必须要进行排序好后的数据如从小到大或者大到小,如果为无序则整个算法则会失效。

01. 算法的思路

找中折半,这是二分法的主要精髓所在,给定一个数组查找数组的中间件数值从而再选择读取判断是否满足条件如果不满足根据大小判断查找下一片区域。

image.png

对于分查找法的结束情况为 x 查找到元素或者 y 小于查找元素,二分查找法在时间复杂度上为 O(logn)性能优于迭代循环的方式。

比如给定一个数组小到大的顺序,基于闭包的方式实现二分查找法【查找的数值为23】

arr := []int{1, 4, 5, 7, 9, 10 ,11, 23, 45}

首先程序先获取整个数组的中位即 middle = len(arr) / 2【注意:首次查询的范围为 1 ~ 45 的值】

image 1.png

然后进行数值判断 arr[middle] == 23 发现不满足这个时候就会再判断是大于还是小于 arr[middle] > 23, 依据结果是小于这个时候程序的扫码范围就在 10~45之间。9 是运算过的因此不会计算在范围内

image 2.png

然后根据10~45的下标继续计算直到找到匹配元素后才结束(或者无找到结果则停止)。

02. 实现代码

// order
const (
	// 小到大
	LittleEndian = iota
	// 大到小
	BigEndian
)
// BinarySearchInt32
//
//	@Description: 二分查找元素是否存在
//	@param arr
//	@param target
//	@return int
func BinarySearchInt32(arr []int32, target int32, order int) int {
	n, _, _ := BinarySearchInt32AndCoord(arr, target, order)
	return n
}

// BinarySearchInt32AndCoord
//
//	@Description: 二分查找元素是否存在,并返回查找的最终位置
//	@param arr
//	@param target
//	@return n
//	@return left
//	@return right
func BinarySearchInt32AndCoord(arr []int32, target int32, order int) (n int, left int, right int) {
	switch order {
	case BigEndian:
		return bigBinarySearchInt32(arr, target, 0, len(arr)-1)
	case LittleEndian:
		return littleBinarySearchInt32(arr, target, 0, len(arr)-1)
	}
	return -1, 0, 0
}

// littleBinarySearchInt32
//
//	@Description: 二分查找元素
//	@param arr 数据集合 | 小到大的顺序
//	@param target 查找数据
//	@param idx 开始坐标
//	@param last 结束坐标
//	@return n 元素位置
//	@return left 查找结束位置 - 左边
//	@return right 查找结束位置 - 右边
func littleBinarySearchInt32(arr []int32, target int32, idx int, last int) (n int, left int, right int) {
	// 终止条件
	if idx > last {
		return -1, last, idx
	}

	middle := (idx + last) / 2

	// 找到元素的情况
	if arr[middle] == target {
		return middle, idx, last
	}
	// 未找到
	if arr[middle] < target {
		return littleBinarySearchInt32(arr, target, middle+1, last)
	} else {
		return littleBinarySearchInt32(arr, target, idx, middle-1)
	}
}

// bigBinarySearchInt32
//
//	@Description: 二分查找元素
//	@param arr 数据集合 | 大到小的顺序
//	@param target 查找数据
//	@param idx 开始坐标
//	@param last 结束坐标
//	@return n 元素位置
//	@return left 查找结束位置 - 左边
//	@return right 查找结束位置 - 右边
func bigBinarySearchInt32(arr []int32, target int32, idx int, last int) (n int, left int, right int) {
	// 终止条件
	if idx < last {
		return -1, idx, last
	}

	middle := (idx + last) / 2
	// 找到元素的情况
	if arr[middle] == target {
		return middle, idx, last
	}
	// 未找到
	if arr[middle] < target {
		return bigBinarySearchInt32(arr, target, idx, middle-1)
	} else {
		return bigBinarySearchInt32(arr, target, middle+1, last)
	}
}

代码的实现上采取闭包的方式进行实现,需注意数组的排序问题大到小与小到大的运算思路是一样的但是其结果会有差别因此定义两个常量做区分。

在代码中还提供对查找的元素的左右值的下标进行返回,这样如果在查找的元素未找到也可以通过根据左右下标值确定目标元素的范围,在一些场景下也是会有相对应的需求。

03. 针对任意有序切片使用二分查找法

另外还可以对该方案进行优化,在程序的实现上本质主要是针对于下标查找数组中的元素,也就是即对程序实现最关键性的值则就是数组的长度,而数组元素本身其实并不重要。对于元素的校验本身对程序而言也并非关键,最为关键的是校验后的结果因此根据这个几个特点。

我们可以将整个二分查找法中的元素校验交由调用者自定义,这样就可以做到对任意数组切片都可以使用二分查找法。(但前提是元素本身具有有序性)

type Compare func(middle int) int

func BinarySearchAny(len int, order int, compare Compare) int {
	if len == 0 {
		return -1
	}

	n := -1
	switch order {
	case LittleEndian:
		n, _, _ = LittleBinarySearchAny(0, len-1, compare)
	case BigEndian:
		n, _, _ = BigBinarySearchAny(0, len-1, compare)
	}
	return n
}

// littleBinarySearchAny
//
//	@Description: 二分查找元素,基于给定的compare方法比较查找目标元素位置 | 从小到大的顺序
//	@param idx 开始坐标
//	@param last 结束坐标
//	@param compare 比较方法
//	@return n 元素位置
//	@return left 查找结束位置 - 左边
//	@return right 查找结束位置 - 右边
func LittleBinarySearchAny(idx int, last int, compare Compare) (n int, left int, right int) {
	// 终止条件
	if idx > last {
		return -1, last, idx
	}
	// 中位
	middle := (idx + last) / 2
	// 比较结果 | 中位 与 目标值比较大小
	result := compare(middle)
	// 处理
	switch result {
	case EQ:
		// 等于
		return middle, idx, last
	case GT:
		// 大于
		return LittleBinarySearchAny(idx, middle-1, compare)
	case LT:
		// 小于
		return LittleBinarySearchAny(middle+1, last, compare)
	}

	return -1, 0, 0
}

// bigBinarySearchAny 二分查找元素,基于给定的compare方法比较查找目标元素位置 | 从大到小的顺序排序
//
//	@param idx 开始坐标
//	@param last 结束坐标
//	@param compare 比较方法
//	@return n 元素位置
//	@return left 查找结束位置 - 左边
//	@return right 查找结束位置 - 右边
func BigBinarySearchAny(idx int, last int, compare Compare) (n int, left int, right int) {
	// 终止条件
	if idx < last {
		return -1, idx, last
	}
	// 中位
	middle := (idx + last) / 2
	// 比较结果 | 中位 与 目标值比较大小
	result := compare(middle)
	// 处理
	switch result {
	case EQ:
		// 等于
		return middle, idx, last
	case GT:
		// 大于
		return BigBinarySearchAny(middle+1, last, compare)
	case LT:
		// 小于
		return BigBinarySearchAny(idx, middle-1, compare)
	}
	return -1, 0, 0
}