码迷,mamicode.com
首页 > 其他好文 > 详细

#转 二分查找

时间:2015-02-02 12:13:14      阅读:153      评论:0      收藏:0      [点我收藏+]

标签:

二分查找

 

二分查找算法基本思想

二分查找算法的前置条件是,一个已经排序好的序列(在本篇文章中为了说明问题的方便,假设这个序列是升序排列的),这样在查找所要查找的元素时,首先与序列中间的元素进行比较,如果大于这个元素,就在当前序列的后半部分继续查找,如果小于这个元素,就在当前序列的前半部分继续查找,直到找到相同的元素,或者所查找的序列范围为空为止.

 

用伪代码来表示, 二分查找算法大致是这个样子的:

 1 left = 0, right = n -1  
 2 
 3 while (left <= right)  
 4 
 5     mid = (left + right) / 2  
 6 
 7     case  
 8 
 9         x[mid] < t:    left = mid + 1;  
10 
11         x[mid] = t:    p = mid; break;  
12 
13         x[mid] > t:    right = mid -1;  
14 
15   
16 
17 return -1;

 

 

第一个正确的程序

根据前面给出的算法思想和伪代码, 我们给出第一个正确的程序,但是,它还有一些小的问题,后面会讲到:

 1 int search(int array[], int n, int v)  
 2 
 3 {  
 4 
 5     int left, right, middle;  
 6 
 7   
 8 
 9     left = 0, right = n - 1;  
10 
11   
12 
13     while (left <= right)  
14 
15     {  
16 
17         middle = (left + right) / 2;  
18 
19         if (array[middle] > v)  
20 
21         {  
22 
23             right = middle;  
24 
25         }  
26 
27         else if (array[middle] < v)  
28 
29         {  
30 
31             left = middle;  
32 
33         }  
34 
35         else  
36 
37         {  
38 
39             return middle;  
40 
41         }  
42 
43     }  
44 
45   
46 
47     return -1;  
48 
49 }  

 

下面,讲讲在编写二分查找算法时可能出现的一些问题.

 

边界错误造成的问题

 

二分查找算法的边界,一般来说分两种情况,一种是左闭右开区间,类似于[left, right),一种是左闭右闭区间,类似于[left, right].需要注意的是, 循环体外的初始化条件,与循环体内的迭代步骤, 都必须遵守一致的区间规则,也就是说,如果循环体初始化时,是以左闭右开区间为边界的,那么循环体内部的迭代也应该如此.如果两者不一致,会造成程序的错误.比如下面就是错误的二分查找算法:

 1 int search_bad(int array[], int n, int v)  
 2 
 3 {  
 4 
 5     int left, right, middle;  
 6 
 7   
 8 
 9     left = 0, right = n;  
10 
11   
12 
13     while (left < right)  
14 
15     {  
16 
17         middle = (left + right) / 2;  
18 
19         if (array[middle] > v)  
20 
21         {  
22 
23             right = middle - 1;  
24 
25         }  
26 
27         else if (array[middle] < v)  
28 
29         {  
30 
31             left = middle + 1;  
32 
33         }  
34 
35         else  
36 
37         {  
38 
39             return middle;  
40 
41         }  
42 
43     }  
44 
45   
46 
47     return -1;  
48 
49 }

 

 

   

这个算法的错误在于, 在循环初始化的时候,初始化right=n,也就是采用的是左闭右开区间,而当满足array[middle] > v的条件是, v如果存在的话应该在[left, middle)区间中,但是这里却把right赋值为middle - 1了,这样,如果恰巧middle-1就是查找的元素,那么就会找不到这个元素.

 

下面给出两个算法, 分别是正确的左闭右闭和左闭右开区间算法,可以与上面的进行比较:

 

 

  1 int search2(int array[], int n, int v)  
  2 
  3 {  
  4 
  5     int left, right, middle;  
  6 
  7   
  8 
  9     left = 0, right = n - 1;  
 10 
 11   
 12 
 13     while (left <= right)  
 14 
 15     {  
 16 
 17         middle = (left + right) / 2;  
 18 
 19         if (array[middle] > v)  
 20 
 21         {  
 22 
 23             right = middle - 1;  
 24 
 25         }  
 26 
 27         else if (array[middle] < v)  
 28 
 29         {  
 30 
 31             left = middle + 1;  
 32 
 33         }  
 34 
 35         else  
 36 
 37         {  
 38 
 39             return middle;  
 40 
 41         }  
 42 
 43     }  
 44 
 45   
 46 
 47     return -1;  
 48 
 49 }  
 50 
 51 /*------------华丽的分割线----------------------------*/  
 52 
 53 int search3(int array[], int n, int v)  
 54 
 55 {  
 56 
 57     int left, right, middle;  
 58 
 59   
 60 
 61     left = 0, right = n;  
 62 
 63   
 64 
 65     while (left < right)  
 66 
 67     {  
 68 
 69         middle = (left + right) / 2;  
 70 
 71   
 72 
 73         if (array[middle] > v)  
 74 
 75         {  
 76 
 77             right = middle;  
 78 
 79         }  
 80 
 81         else if (array[middle] < v)  
 82 
 83         {  
 84 
 85             left = middle + 1;  
 86 
 87         }  
 88 
 89         else  
 90 
 91         {  
 92 
 93             return middle;  
 94 
 95         }  
 96 
 97     }  
 98 
 99   
100 
101     return -1;  
102 
103 }  

 

死循环

 

上面的情况还只是把边界的其中一个写错, 也就是右边的边界值写错, 如果两者同时都写错的话,可能会造成死循环,比如下面的这个程序:

 

 

 1 int search_bad2(int array[], int n, int v)  
 2 
 3 {  
 4 
 5     int left, right, middle;  
 6 
 7   
 8 
 9     left = 0, right = n - 1;  
10 
11   
12 
13     while (left <= right)  
14 
15     {  
16 
17         middle = (left + right) / 2;  
18 
19         if (array[middle] > v)  
20 
21         {  
22 
23             right = middle;  
24 
25         }  
26 
27         else if (array[middle] < v)  
28 
29         {  
30 
31             left = middle;  
32 
33         }  
34 
35         else  
36 
37         {  
38 
39             return middle;  
40 
41         }  
42 
43     }  
44 
45   
46 
47     return -1;  
48 
49 }  

 

这个程序采用的是左闭右闭的区间.但是,当array[middle] > v的时候,那么下一次查找的区间应该为[middle + 1, right], 而这里变成了[middle, right];当array[middle] < v的时候,那么下一次查找的区间应该为[left, middle - 1], 而这里变成了[left, middle].两个边界的选择都出现了问题, 因此,有可能出现某次查找时始终在这两个范围中轮换,造成了程序的死循环.

 

溢出

 

前面解决了边界选择时可能出现的问题, 下面来解决另一个问题,其实这个问题严格的说不属于算法问题,不过我注意到很多地方都没有提到,我觉得还是提一下比较好.

在循环体内,计算中间位置的时候,使用的是这个表达式:

1 middle = (left + right) / 2;  

 

 

假如,left与right之和超过了所在类型的表示范围的话,那么middle就不会得到正确的值.

所以,更稳妥的做法应该是这样的。

1 middle = left + (right - left) / 2;  

 

更完善的算法

 

前面我们说了,给出的第一个算法是一个"正确"的程序, 但是还有一些小的问题。

首先, 如果序列中有多个相同的元素时,查找的时候不见得每次都会返回第一个元素的位置, 比如考虑一种极端情况:序列中都只有一个相同的元素,那么去查找这个元素时,显然返回的是中间元素的位置.

其次, 前面给出的算法中,每次循环体中都有三次情况,两次比较,有没有办法减少比较的数量进一步的优化程序?

<<编程珠玑>>中给出了解决这两个问题的算法,结合前面提到溢出问题我对middle的计算也做了修改:

 3 int search4(int array[], int n, int v)  
 4 
 5 {  
 6 
 7     int left, right, middle;  
 8 
 9   
10 
11     left = -1, right = n;  
12 
13   
14 
15     while (left + 1 != right)  
16 
17     {  
18 
19         middle = left + (right - left) / 2;  
20 
21   
22 
23         if (array[middle] < v)  
24 
25         {  
26 
27             left = middle;  
28 
29         }  
30 
31         else  
32 
33         {  
34 
35             right = middle;  
36 
37         }  
38 
39     }  
40 
41   
42 
43     if (right >= n || array[right] != v)  
44 
45     {  
46 
47         right = -1;  
48 
49     }  
50 
51   
52 
53     return right;  
54 
55 }  

 

以上这个最精简

#转 二分查找

标签:

原文地址:http://www.cnblogs.com/awsent/p/4267202.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!