您的位置:首页 > 其它

无处不在的二分搜索

2012-05-14 10:21 113 查看
二分搜索想必学计算机的人都很熟了,它是很多算法里的都会用到的,编程珠玑里有专门的介绍。二分搜索的框架大家都很熟悉,但要完完整整写对也不是那么容易的事情。这很考验一个人的基本功的,这也是面试官常考的一个问题。下面就对其进行一下总结。

1. 常规写法

int BinarySearch(int *a, int l, int r, int z){
if(l > r)
return -1;
else if(l == r)
if(a[l] == z)
return l;
else
return -1;
else{
int mid;
while(l <= r){
mid = (l + r)/2;
if(a[mid] < z)
l = mid +1;
else if(a[mid] > z)
r = mid - 1;
else
return mid;
}
return -1;
}
}

这里需要注意几点:

(1)mid的取法,是取上整(mid = (l + r +1)/2)还是取下整(mid = (l + r )/2)。这里是取下整,这意味着mid总是倾向于偏小的索引;否则,mid

倾向于偏大的索引 。

(2)l 或 r 的更新。当mid倾向于偏小值时(取下整),当查找值比中间值还大时( a[mid] < z ),l 要倾向于偏大值,所以 l = mid + 1,不然就可能出现mid索引值不变导致死循环;相反,当mid倾向于偏大值时(取上整),当查找值比中间值还小时(
a[mid] > z ),r 要倾向于偏小值,所以 r = mid - 1。

现在再说说二分搜索算法的改进。使内层循环更紧凑。这里内层用的是三路比较,现在改进为二路比较。

int BinarySearch(int *a, int l, int r, int z){
if(l > r)
return -1;
else if(l == r)
if(a[l] == z)
return l;
else
return -1;
else{
int mid;
while(l < r){
mid = (l + r)/2;
if(a[mid] < z)
l = mid +1;
else
r = mid;
}
if(a[l] == z)
return l;
return -1;
}
}


同样,这里还是要注意前面提到的那两点。不同的是,(1)如果查找值不比中间值大,即 if(a[mid] < z) 不成立的话,r = mid ,包含了<=两个情况;(2)while循环的条件变成了 l < r ,l == r 变成了终止条件,放到外面;(3)while循环外面的 if 判断,当 l == r 时,a[l] 和 a[r] 是否都指向要查找的值。

二分查找改进版

当数组中有重复元素时,返回满足条件的最右元素或最左元素。我自己写了个java实现版

class BinarySearch {
//在大小为n的数组a中查找值为n的元素,返回满足条件的最右元素
public static int binarySearchR(int[] a, int x, int n) {
if(n > 0 && x >= a[0]) {
int left = 0;
int right = n-1;
while(left < right) {
int middle = (left + right + 1)/2;
if(x < a[middle])
right = middle - 1;
else
left = middle;
}
if(x == a[left])
return left;
}
return -1;
}

//在大小为n的数组a中查找值为n的元素,返回满足条件的最左元素
public static int binarySearchL(int[] a, int x, int n) {
if(n > 0 && x >= a[0]) {
int left = 0;
int right = n-1;
while(left < right) {
int middle = (left + right)/2;
if(x > a[middle])
left = middle + 1;
else
right = middle;

}
if(x == a[left])
return left;
}
return -1;
}
}
public class BinarySearchTest {

/**
* @param args
*/
public static void main(String[] args) {
int[] a  = {1,1,6,6,6,6,6,8,8};

System.out.println(BinarySearch.binarySearchL(a, 1, 9));
System.out.println(BinarySearch.binarySearchR(a, 1, 9));
System.out.println(BinarySearch.binarySearchL(a, 6, 9));
System.out.println(BinarySearch.binarySearchR(a, 6, 9));
System.out.println(BinarySearch.binarySearchL(a, 8, 9));
System.out.println(BinarySearch.binarySearchR(a, 8, 9));
}

}


近日看过一篇博问,里面讲到二分查找中容易忽略的一个错误——数组下标上溢。当给middle赋值时,middle = (left + right)/2 或 middle = (left + right + 1)/2,如果数组很大的话,left + right可能上溢。

所以建议在给给middle赋值时,采用 middle = left + ( right - left )/2 或
middle = left + ( right - left + 1)/2。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: