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;

第一個正確的程式 根據前面給出的演算法思想和虛擬碼, 我們給出第一個正確的程式,但是,它還有一些小的問題,後面會講到

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