转载http://www.cppblog.com/converse/archive/2009/10/05/97905.html
二分查找算法基本思想 二分查找算法的前置条件是,一个已经排序好的序列(在本篇文章中为了说明问题的方便,假设这个序列是升序排列的),这样在查找所要查找的元素时,首先与序列中间的元素进行比较,如果大于这个元素,就在当前序列的后半部分继续查找,如果小于这个元素,就在当前序列的前半部分继续查找,直到找到相同的元素,或者所查找的序列范围为空为止. 用伪代码来表示, 二分查找算法大致是这个样子的:
1 left = 0, right = n -1
2 while (left <= right)
3 mid = (left + right) / 2
4 case
5 x[mid] < t: left = mid + 1;
6 x[mid] = t: p = mid; break;
7 x[mid] > t: right = mid -1;
8
9 return -1;
第一个正确的程序 根据前面给出的算法思想和伪代码, 我们给出第一个正确的程序,但是,它还有一些小的问题,后面会讲到
int search(int array[], int n, int v)
{
int left, right, middle;
left = 0, right = n - 1;
while (left <= right)
{
middle = (left + right) / 2;
if (array[middle] > v)
{
right = middle;
}
else if (array[middle] < v)
{
left = middle;
}
else
{
return middle;
}
}
return -1;
}
下面,讲讲在编写二分查找算法时可能出现的一些问题. 边界错误造成的问题 二分查找算法的边界,一般来说分两种情况,一种是左闭右开区间,类似于[left, right),一种是左闭右闭区间,类似于[left, right].需要注意的是, 循环体外的初始化条件,与循环体内的迭代步骤, 都必须遵守一致的区间规则,也就是说,如果循环体初始化时,是以左闭右开区间为边界的,那么循环体内部的迭代也应该如此.如果两者不一致,会造成程序的错误.比如下面就是错误的二分查找算法:
这个算法的错误在于, 在循环初始化的时候,初始化right=n,也就是采用的是左闭右开区间,而当满足array[middle] > v的条件是, v如果存在的话应该在[left, middle)区间中,但是这里却把right赋值为middle - 1了,这样,如果恰巧middle-1就是查找的元素,那么就会找不到这个元素.
下面给出两个算法, 分别是正确的左闭右闭和左闭右开区间算法,可以与上面的进行比较:
(下面这两个算法是正确的)
死循环 上面的情况还只是把边界的其中一个写错, 也就是右边的边界值写错, 如果两者同时都写错的话,可能会造成死循环,比如下面的这个程序:
1 int search_bad2(int array[], int n, int v)
2 {
3 int left, right, middle;
4
5 left = 0, right = n - 1;
6
7 while (left <= right)
8 {
9 middle = (left + right) / 2;
10 if (array[middle] > v)
11 {
12 right = middle;
13 }
14 else if (array[middle] < v)
15 {
16 left = middle;
17 }
18 else
19 {
20 return middle;
21 }
22 }
23
24 return -1;
25 }
这个程序采用的是左闭右闭的区间.但是,当array[middle] > v的时候,那么下一次查找的区间应该为[middle + 1, right], 而这里变成了[middle, right];当array[middle] < v的时候,那么下一次查找的区间应该为[left, middle - 1], 而这里变成了[left, middle].两个边界的选择都出现了问题, 因此,有可能出现某次查找时始终在这两个范围中轮换,造成了程序的死循环. 溢出 前面解决了边界选择时可能出现的问题, 下面来解决另一个问题,其实这个问题严格的说不属于算法问题,不过我注意到很多地方都没有提到,我觉得还是提一下比较好. 在循环体内,计算中间位置的时候,使用的是这个表达式:
middle = (left + right) / 2;
假如,left与right之和超过了所在类型的表示范围的话,那么middle就不会得到正确的值. 所以,更稳妥的做法应该是这样的:
middle = left + (right - left) / 2;
更完善的算法 前面我们说了,给出的第一个算法是一个"正确"的程序, 但是还有一些小的问题. 首先, 如果序列中有多个相同的元素时,查找的时候不见得每次都会返回第一个元素的位置, 比如考虑一种极端情况:序列中都只有一个相同的元素,那么去查找这个元素时,显然返回的是中间元素的位置. 其次, 前面给出的算法中,每次循环体中都有三次情况,两次比较,有没有办法减少比较的数量进一步的优化程序? <<编程珠玑>>中给出了解决这两个问题的算法,结合前面提到溢出问题我对middle的计算也做了修改:
1 int search4(int array[], int n, int v)
2 {
3 int left, right, middle;
4
5 left = -1, right = n;
6
7 while (left + 1 != right)//这个循环维持的条件是left<right && array[left]<v<=array[right],所以到最后的时候,
8 {//如果可以找到目标,则只剩下两个数,并且满足 array[left]<v<=array[right],是要查找的数是right
9 middle = left + (right - left) / 2;
10
11 if (array[middle] < v)//必须保证array[left]<v<=array[right],所以left = middle;
12 {//如果left =middle+1,则有可能出现 array[left]<=v的情况
13 left = middle;
14 }
15 else
16 {
17 right = middle;
18 }
19 }
20
21 if (right >= n || array[right] != v)
22 {
23 right = -1;
24 }
25
26 return right;
27 }
这个算法是所有这里给出的算法中最完善的一个,正确,精确且效率高.
但是这个算法的还是不能很好的理解
可以用下面的算法,可以找出满足条件的数
1 int Bi_Search(int a[],int n,int b)//
2 {//返回等于b的第一个
3 if(n==0)
4 return -1;
5 int low = 0;
6 int high = n-1;
7 int last = -1;//用last记录上一次满足条件的下标
8 while (low<=high)
9 {
10 int mid = low +(high-low)/2;
11 if (a[mid]==b)
12 {
13 last = mid;
14 high = mid -1;
15 }
16 else if(a[mid]>b)
17 high = mid -1;
18 else
19 low = mid +1;
20 }
21
22 return last;
23
24 }
25 int Bi_Search1(int a[],int n,int b)//大于b的第一个数
26 {
27 if(n<=0)
28 return -1;
29 int last = -1;
30 int low = 0;
31 int high = n-1;
32 while (low<=high)
33 {
34 int mid = low +(high - low)/2;
35 if(a[mid]>b)
36 {
37 last = mid;
38 high = mid -1;
39 }
40 else if (a[mid]<=b)
41 {
42 low =mid +1;
43 }
44 }
45
46 return last;
47 }
48 int Bi_Search(int a[],int n,int b)//
49 {//返回等于b的第一个
50 if(n==0)
51 return -1;
52 int low = 0;
53 int high = n-1;
54 int last = -1;//用last记录上一次满足条件的下标
55 while (low<=high)
56 {
57 int mid = low +(high-low)/2;
58 if (a[mid]==b)
59 {
60 last = mid;
61 high = mid -1;
62 }
63 else if(a[mid]>b)
64 high = mid -1;
65 else
66 low = mid +1;
67 }
68
69 return last;
70
71 }
72 int Bi_Search1(int a[],int n,int b)//大于b的第一个数
73 {
74 if(n<=0)
75 return -1;
76 int last = -1;
77 int low = 0;
78 int high = n-1;
79 while (low<=high)
80 {
81 int mid = low +(high - low)/2;
82 if(a[mid]>b)
83 {
84 last = mid;
85 high = mid -1;
86 }
87 else if (a[mid]<=b)
88 {
89 low =mid +1;
90 }
91 }
92
93 return last;
94 }View Code