二分查找算法基本思想


  1.  

轉載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;
View Code

 



第一個正確的程序
根據前面給出的算法思想和偽代碼, 我們給出第一個正確的程序,但是,它還有一些小的問題,后面會講到

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 }
View Code

 


這個程序采用的是左閉右閉的區間.但是,當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 
View Code

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM