功能描述
二分查找仅针对有序排列(正序或倒序)的数组,通过比较中间元素和待查找元素的大小,从而确定下一步是在左半边还是右半边继续查找,直到找到要找的元素或者序列长度为0或1而不能再继续二分下去。这里采用在内存空间中连续存储的数组形式存储数据,方便快速定位中间元素而不需要从头遍历。程序结束之后给出待查找元素的位置信息或未找到该元素的信号。
本文是我在学习二分查找实现过程中的总结,如果有不完善或错误的地方欢迎指正。
算法实现过程
为了方便划定查找范围,设置一个头指针front(记录下标的整数)指向要查找的数组的第一个元素,设置一个尾指针rear(记录下标的整数)指向该数组最后一个元素的下一个位置,中间位置由
middle = (front + rear) / 2; //中间位置
得到。注意数组元素的位置即下标,是从0开始的。以[1, 2, 3]为例可以得到,middle = (front + rear) / 2 = (0 + 3) / 2 = 1
,即元素2是中间位置的元素;以[1, 2]为例,middle = (front + rear) / 2 = (0 + 2) / 2 = 1
,即认为元素1是中间的位置。可以看到,对于奇数个元素的数组,中间元素是处于中轴线上的元素,对于元素个数为偶数的数组,中间元素是中轴线右边的元素。对于下标不是从零开始的数组片段也是一样的道理。
如果中间元素正好等于待查找的元素,那么程序直接返回中间元素的下标,皆大欢喜;如果中间元素大于待查找元素,说明后者有可能出现在以中间元素为分界线的左侧数组中,此时已中间指针赋值尾指针,正好就是左侧数组的首尾指针范围;如果中间元素小于待查找元素,说明需要在右侧数组中继续查找,此时可将中间指针的下一个位置赋值给头指针,尾指针不变。上述过程很容易联想到通过递归实现,将首指针、尾指针作为参数传入查找函数中。
这里我们考虑几个终止递归的条件。当数组长度为1时,front == rear == middle
,如果该元素大于待查找元素,则需要在左侧数组查找,此时赋值rear = middle;
后陷入死循环;如果该元素小于待查找元素,则需要在右侧数组查找,此时rear = middle + 1
,赋值之后的rear向后移了一位,数组越界。当数组长度为2时,即rear == front + 1
,此时同样会遇到上面的问题。所以,程序需要注意避免死循环和数组越界的问题。
代码展示
这里我以[2, 4, 6]作为演示数组。代码暂不考虑效率问题和性能优化。分别输入3、7、2、4、6作为待查找元素,3可以测试可能导致死循环的异常情况,7可以测试可能导致数组越界的异常情况,2、4、6分别测试三种找到元素的情况。测试之后发现都没问题。这里就不贴主程序代码了,只贴两个函数的代码。
递归方法实现
//二分法递归查找正序排列数组
//输入:待查找数组,头指针,尾指针,待查找元素
//输出:待查找元素的位置或0
public int recBinarySearch(int[] arrInAscendingOrder, int front, int rear, int elementToFind) {
int middle = (front + rear) / 2; //中间位置
//结束递归的条件
if (middle == front) {
if (arrInAscendingOrder[front] == elementToFind) return middle;
else return -1; //避免死循环
}
if (middle == rear) {
if (arrInAscendingOrder[middle] == elementToFind) return middle;
else return -1; // 避免数组越界
}
//递归
if (arrInAscendingOrder[middle] > elementToFind) return recBinarySearch(arrInAscendingOrder, front, middle, elementToFind);
if (arrInAscendingOrder[middle] < elementToFind) return recBinarySearch(arrInAscendingOrder, middle + 1, rear, elementToFind);
if (arrInAscendingOrder[middle] == elementToFind) return middle;
return -1;
}
非递归方法实现
//二分法非递归查找正序排列数组
//输入:待查找数组,待查找元素
//输出:待查找元素的位置或0
public int nonRecBinarySearch(int[] arrInAscendingOrder, int element) {
int front = 0; // 头指针
int rear = arrInAscendingOrder.length; // 尾指针
int middle = 0; // 中间指针
while (true) {
middle = (front + rear) / 2;
if (arrInAscendingOrder[middle] != element) {
if (arrInAscendingOrder[middle] > element) {
if (front == middle) return -1;
rear = middle;
} else {
if (rear == middle) return -1;
front = middle + 1;
}
} else {
return middle;
}
}
}
后记
为什么我写完之后感觉递归比非递归好写多了……虽然空间占用上可能会爆炸……作为一个student我要好好珍惜暂时不用考虑这个问题的时光_(:з」∠)_