青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

那誰的技術博客

感興趣領域:高性能服務器編程,存儲,算法,Linux內核
隨筆 - 210, 文章 - 0, 評論 - 1183, 引用 - 0
數據加載中……

把二分查找算法寫正確需要注意的地方

今天再次解決一個需要使用二分查找的問題,再一次的,我又沒有一次過寫對.(為什么我說"又"?)

抓狂了,似乎開始有一些"二分查找恐懼癥".

為了以后能夠一次將這個基本的算法寫對,我決定再仔細研究一下.我之前有寫過一個二分查找的算法,在這里,這一次再以這個問題為例來說明.

我今早寫下的錯誤代碼類似于下面的樣子:
#include <stdio.h>

int search(int array[], int n, int v)
{
    
int left, right, middle;

    left 
= 0, right = n;

    
while (left < right)
    {
        middle 
= (left + right) / 2;
        
if (array[middle] > v)
        {
            right 
= middle - 1;
        }
        
else if (array[middle] < v)
        {
            left 
= middle + 1;
        }
        
else
        {
            
return middle;
        }
    }

    
return -1;
}

int main()
{
    
int array[] = {012345671319};

    
int m = search(array, sizeof(array)/sizeof(array[0]), 1);

    printf(
"m = %d\n", m);

    
return 0;
}

實際上,如果使用測試用例來測試,這個算法并不是在所有情況下都會出錯的,還是有時可以得到正確的結果的.但是,你能看出來它錯在哪兒嗎?

在這里,循環的開始處,把循環遍歷的序列區間是這樣的:
left =0, right = n;
while (left < right)
{
    
// 循環體
}
也就是說,這是一個左閉右開的區間:[0, n).

但是,在循環內部, 卻不是這樣操作的:
        middle = (left + right) / 2;

        
if (array[middle] > v)
        {
            right 
= middle - 1;
        }
        
else if (array[middle] < v)
        {
            left 
= middle + 1;
        }
        
else
        {
            
return middle;
        }
當array[middle] > v條件滿足時, 此時v如果存在的話必然在左閉右開區間[left, middle)中, 因此,當這個條件滿足時, right應該為middle, 而在這里, right賦值為middle - 1了, 那么, 就有可能遺漏array[middle - 1] = v的情況.

因此,這種錯誤的寫法并不是在所有的情況下都會出錯,有時還是可以找到正確的結果的.

這是一種典型的二分查找算法寫錯的情況,循環體是左閉右開區間,而循環體內部卻是采用左閉右閉區間的算法進行操作.
下面給出的兩種正確的算法,算法search是左閉右閉區間算法,而算法search2是左閉右開區間算法,可以對比一下差異.
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 - 1;
        }
        
else if (array[middle] < v)
        {
            left 
= middle + 1;
        }
        
else
        {
            
return middle;
        }
    }

    
return -1;
}

int search2(int array[], int n, int v)
{
    
int left, right, middle;

    left 
= 0, right = n;

    
while (left < right)
    {
        middle 
= (left + right) / 2;

        
if (array[middle] > v)
        {
            right 
= middle;
        }
        
else if (array[middle] < v)
        {
            left 
= middle + 1;
        }
        
else
        {
            
return middle;
        }
    }

    
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;
}
為什么會造成死循環?

從循環條件來看,這個算法的操作區間是左閉右閉區間的,因此當array[middle] > v時,v如果存在的話應該在[left, middle- 1]中,因此此時right應該是middle - 1,而不是middle;類似的,當array[middle] < v時,下一次操作的區間應該是[middle + 1, right]中.而當元素不存在這個序列中時,算法在一個錯誤的區間中循環,但是又不能終止循環,于是就造成了死循環.

因此,要將二分查找算法寫對,其實很多人都大概知道思想,具體到編碼的時候,就會被這些看似微小的地方搞糊涂.因此,需要注意這一點:
算法所操作的區間,是左閉右開區間,還是左閉右閉區間,這個區間,需要在循環初始化,循環體是否終止的判斷中,以及每次修改left,right區間值這三個地方保持一致,否則就可能出錯.



posted on 2009-09-21 23:46 那誰 閱讀(11200) 評論(29)  編輯 收藏 引用 所屬分類: 算法與數據結構

評論

# re: 把二分查找算法寫正確需要注意的地方  回復  更多評論   

因為二分查找的思想很簡單, 很多人稍微看看就開始編碼了, 沒有考慮:
1. 每次遞歸中,區間如何劃分
2. 遞歸的邊界有哪些,是否能達到
3. 查找的值存在多個時, 將取得哪一個

仔細推敲邊界的人不多。 大多都是隨手寫寫, 最多加幾個測試數據。
區間劃分, 我只在少數幾個地方看到是被“二分”, 普遍做法是“三分”。
少數幾個地方是《代碼之美》;cplusplus網站中lower_bound,upper_bound的偽代碼。
討論多值取向的文章就更少了。
2009-09-22 00:04 | OwnWaterloo

# re: 把二分查找算法寫正確需要注意的地方  回復  更多評論   

@OwnWaterloo
汗,你回復的真快....
2009-09-22 00:07 | 那誰

# re: 把二分查找算法寫正確需要注意的地方  回復  更多評論   

@那誰
cpp的rss比較快。。。


這里有一份代碼:
http://www.cnblogs.com/Devfly/archive/2009/09/18/can-you-write-a-right-binary-search-algorithm.html#1651172

是將閉區間[lower,upper], 取m = (lower + upper)/2
分為2個區間[lower,m] ; [m+1,upper]

遞歸邊界是區間只含一個元素: [lower,lower]

取向是返回[lower,upper]中大于等于target的index。
遞歸邊界一定能達到很好證明, 取向比較麻煩。


而其他一些常見的區間取法, 比如[first,last),
還有中值的取法,比如 (lower + upper + 1)/2, 或者用那個什么黃金分割……
以及多值時的取向, 隨機, 第1個, 最后1個。
它們與stl中lower_bound, upper_bound的關系
等等…… 都挺有意思的……
不過最近有其他事要忙…… 只好終止了……

你感興趣的話可以研究研究, 我就直接撿個現成了~~~
2009-09-22 00:25 | OwnWaterloo

# re: 把二分查找算法寫正確需要注意的地方  回復  更多評論   

編程珠璣上面專門有一章講這個,呵呵。
另外wiki頁面上提到一點,three-way/two-way比較的區別,個人感覺正是two-way比較造成了區間劃分和邊界設定容易混亂。不過three-way也一樣。比較欣賞stl里面lower和upper的寫法。
另外那個多值取向的問題確實折騰了我好久,也沒什么很優雅的解決辦法。
2009-09-22 05:58 | 唐僧

# re: 把二分查找算法寫正確需要注意的地方  回復  更多評論   

@唐僧
大多數情況下,如果存在多值的話,一般取哪個都是無所謂的。
2009-09-22 11:12 | 陳梓瀚(vczh)

# re: 把二分查找算法寫正確需要注意的地方  回復  更多評論   

@唐僧
編程珠璣上有講這個? 第2版中? 第1版已出版很久、很多細節記不清了……
wiki是指編程編程珠璣的wiki? 給個鏈接撒~~~

我第1次發現這個問題是在看《代碼之美》時,當時困惑了很久“難道我以前不是這樣寫的?”。我的確是寫成three-way了。
確實, 如果three-way是聽說二分查找思想后,就能立即編碼的話, two-way就需要深入考究考究才能編寫出來。


two-way就能有明確的取向啊。
對區間[lower,upper]:
1. 如果取中值 m = (lower+upper)/2
將區間劃分為[lower,m],[m+1,upper]
直到區間只有一個元素:[lower,lower]
取向就是從lower到upper, 第1個大于等于target的index。

2. 如果取中值 m = (lower+upper+1)/2
將區間劃分為[lower,m-1],[m,upper]
直到區間只有一個元素:[lower,lower]
取向就是從upper到lower,第1個小于等于target的index。

上面給出了一份代碼的鏈接, 我覺得挺優雅的……
2009-09-22 14:48 | OwnWaterloo

# re: 把二分查找算法寫正確需要注意的地方  回復  更多評論   

@陳梓瀚(vczh)
存在相同鍵的有序序列中,查找取向是有用的。
它能解決:“找鍵等于k的所有值”,“找鍵大于等于p,小于等于q的所有值”, 這種常見的需求。
2009-09-22 14:51 | OwnWaterloo

# re: 把二分查找算法寫正確需要注意的地方  回復  更多評論   

@OwnWaterloo
編程珠璣里面確實有一章講解算法正確性的,是以二分查找算法來講解的.
2009-09-22 15:07 | 那誰

# re: 把二分查找算法寫正確需要注意的地方  回復  更多評論   

@OwnWaterloo
看了你給的帖子,感覺我的算法還有問題,就是在存在多個相同元素的時候沒有返回第一個元素.回頭再研究一下了,目前至少算法是"正確的",只是不夠"精確",哈哈.


2009-09-22 15:13 | 那誰

# re: 把二分查找算法寫正確需要注意的地方  回復  更多評論   

@那誰
第2版還是第1版的編程珠璣? 書中有證明取向性么?
取向其實也只算個附加需求。 只對多值才有用……

我不知道three-way如何寫出固定取向……
只考慮過two-way的幾種區間劃分的取向。
期待你的研究成果~~~

2009-09-22 15:21 | OwnWaterloo

# re: 把二分查找算法寫正確需要注意的地方  回復  更多評論   

@OwnWaterloo
我看的是這本編程珠璣:
http://www.douban.com/subject/3227098/
2009-09-22 15:32 | 那誰

# re: 把二分查找算法寫正確需要注意的地方  回復  更多評論   

@OwnWaterloo
這種情況下應該用具有多重值的map,怎么能給列表增加太多負擔
2009-09-22 17:33 | 陳梓瀚(vczh)

# re: 把二分查找算法寫正確需要注意的地方  回復  更多評論   

@陳梓瀚(vczh)
你再次偏題。怎么又扯上列表了?

多重值的map? 比如std::multimap?
如果使用的是std::multimap, 我相信用得更多的也是lower_bound、upper_bound、equal_range, 而不是find。
同樣說明了對存在相等鍵的序列,查找的取向是有用的。


如果需要一個僅僅構建一次,查找多次,并且含有等值鍵的序列,并不一定需要multimap。
std::lower_bound,std::upper_bound同樣可以對數組或者list查找,僅僅需要序列有序,并不要求序列有其他什么特性, 列表的負擔又從何說起?


如果待查找的序列有相等的鍵, 那么查找算法有一定的取向就是一個有用的需求。跟序列是array、list還是map無關。

2009-09-22 17:53 | OwnWaterloo

# re: 把二分查找算法寫正確需要注意的地方  回復  更多評論   

@OwnWaterloo
是編程珠璣的第二版,http://www.cs.bell-labs.com/cm/cs/pearls/sketch04.html 這個鏈接是目錄,以二分為例講的,writing correct programs。ps, 這本書的影印版有賣的。我手上有一本,晚上回去翻看下,忘記有沒有關于取向性的證明了。
wiki我沒說清楚,是二分查找的wiki頁面。2-way和3-way的討論在Syntax difficulties章節里面 http://en.wikipedia.org/wiki/Binary_search#Syntax_difficulties

@陳梓瀚(vczh)
array list map 不影響搜索算法的本質,只是外殼不同罷了。
如果要搜索的是某個子區間而不是單一值的話,考慮多值取向就很有必要了。

@那誰
我見過的能夠熟練而準確的寫出二分的人都是在高中時候經歷過系統oi訓練的,呵呵。期待你的3-way研究,原文的代碼在大多數情況下已經夠用了,呵呵。
2009-09-22 20:21 | 唐僧

# re: 把二分查找算法寫正確需要注意的地方  回復  更多評論   

@唐僧
謝謝~~

我想到一個證明3-way不可能實現確定取向的方法。 其實非常簡單……

因為一個binary-search算法,必須對所有輸入數據都有確定的取向,才能說該算法有確定取向。
故證明某個binary-search算法不具有確定取向只要構造出一個反例,即可……

比如…… 一個極端的數據:鍵全部相同。 3-way肯定是第1次就退出查找了, 肯定不具有確定取向了。

2009-09-22 20:43 | OwnWaterloo

# re: 把二分查找算法寫正確需要注意的地方  回復  更多評論   

@OwnWaterloo
多謝指點,下午我隱約思考的方向出了問題,果不其然,慣性思維的力量啊。

另外,我去翻了The art of computer programming,發現了更好的解決辦法。
那誰指出的兩個錯誤都是源自 二分前的中值middle 和 二分后的區間邊界bound 的沖突,而考慮到每次二分后的兩個區間,不管二分前元素個數是奇數還是偶數,二分后的兩個區間長度最多相差一,于是可以選擇一個“近似”的中值middle,這個值緊靠較小區間的外側即可。
Knuth命名這種算法叫Uniform binary search,在wiki頁面有c實現。中文環境沒有搜索到相關的討論。 http://en.wikipedia.org/wiki/Uniform_binary_search
寫這樣的代碼難度比一般的二分要大,感覺理解透了應該是最不容易出錯的一種寫法。
2009-09-23 04:58 | 唐僧

# re: 把二分查找算法寫正確需要注意的地方  回復  更多評論   

@唐僧
4點58分的回復……

The art of computer programming …… 一直沒下決心去啃……

這個Uniform binary search是不是將除法操作緩存一下?
其實聰明點的編譯器同樣可以將
i /= 2u; 優化成 shr i
它使用一個預先計算好的middle值的緩存, 即使真能提高一點速度, 但能處理這種情況嗎?
int a[] = { 7, 3, 5, 7, 2 }; 整個a無序, 但a[1] - a[3]升序。
binary_search(keys=a, target=5, lower=1,upper=3 );


確實two-way不好寫。 12點多的時候就去問了一個拿過2次ACM金牌的室友,讓他寫一個二分搜索。
他回答“讓我想一想”時, 我還有點驚喜 —— 之前我也問過一些人, 但終于有一個不是張口(隨手)就給出代碼的人了, 大牛果然不同凡響。
等了一會后, 得到一個3-way的……

打算將數據結構重新學一遍…… 透徹理解一下……
以前學的那叫啥…… 所以嘛, 只能看著室友拿金牌-_-。

2009-09-23 05:51 | OwnWaterloo

# re: 把二分查找算法寫正確需要注意的地方  回復  更多評論   

@唐僧

哦 ……

int a[] = { 7, 3, 5, 7, 2 }; 用Uniform binary search可以如下處理……
binary_search(keys=a+1, target=5, count=3 );

快天亮了, 頭有點暈……
2009-09-23 05:54 | OwnWaterloo

# re: 把二分查找算法寫正確需要注意的地方  回復  更多評論   

@唐僧
再回一貼 ……

Uniform binary search中需要的那個table —— delta, 好像可以用template meta programming在編譯時計算完畢 ……
C++太強大了~_~

否則, 如果運行時計算的話:
1. 將make_delta的工作留給client(就像鏈接中的示例代碼一樣)
會使得unisearch不太好用。多次調用make_delta雖然不會錯, 但賠本了。
如果只調用一次, 時機不好掌握。
如果在main中, main前的執行代碼不能使用unisearch, 而且如果每個庫都要求在main中這么初始化一次, main會受不了的……

2. unisearch自己處理好make_delta
那每次對unisearch的調用,會有一次額外的運行時檢測 if (!is_init) 是逃不掉了。
2009-09-23 06:02 | OwnWaterloo

# re: 把二分查找算法寫正確需要注意的地方  回復  更多評論   

汗,你們倆晚上都不睡覺的嗎,怎么回復時間都在凌晨....
2009-09-23 11:27 | 那誰

# re: 把二分查找算法寫正確需要注意的地方  回復  更多評論   

兩位,似乎還有一個問題,考慮一個極端情況,假如一個序列中都是同樣值的元素,而所需查找的就是這個元素,很顯然,第一次查找就會找到了,但是找到的卻是序列的中間元素.
2009-09-23 11:39 | 那誰

# re: 把二分查找算法寫正確需要注意的地方  回復  更多評論   

@那誰
http://www.shnenglu.com/converse/archive/2009/09/21/96893.aspx#96970
2009-09-23 14:51 | OwnWaterloo

# re: 把二分查找算法寫正確需要注意的地方  回復  更多評論   

@那誰
那個凌晨的時間是我的夜晚,我是GMT+1。其他的回復都是在課間,我現在這課上的,一天7個小時,所以幾乎沒有白天的回復。

@OwnWaterloo
我對于TMP沒有研究,呵呵,就等c++0x了。

delta數組是生成二分點用的,記錄每次二分相對于上一次二分點的偏移,對于一個長度固定N的待搜索數組來說,delta[]是一樣的。而且dleta[]中的元素至多有log(2,N)個,make_delta[]是在數組長度變化之后重新計算的。
比如wiki上的例子,N=10那么delta[]的前幾項即5,3,1,第一次二分點是5,第二次就是5+3=8或者5-3=2,第三次是8+1或者8-1或者2+1或者2-1。
這個計算量并不大,而且我感覺這個算法的目的并不是通過緩存二分點而提高性能,從命名上看uniform的意義好像是尋求一種 形式上 的統一,或者說是讓代碼更優雅吧,同時避免普通二分搜索常見的邊界和中點的混亂。
(我確實搜索到了有文章提及Uniform比普通二分更有效率,一篇討論搜索算法耗能的,energy consomation,沒有細看)
2009-09-23 22:45 | 唐僧

# re: 把二分查找算法寫正確需要注意的地方  回復  更多評論   

@唐僧
看來就我是夜貓了……
說點題外話……
因為上面給出的是遞歸代碼, 所以就稍微查看了一下各種編譯器的優化情況, 有點吃驚……
使用遞歸求階乘的代碼,msvc8,9; gcc 3.4.x可以優化為迭代,vc6不行。
對上面提到的二分搜索的遞歸形式:
tail_recursion

gcc也能將為遞歸優化為迭代。
下面是一個轉化為迭代的版本:
iteration

gcc對這兩者生成的代碼,除了標號不同,其他地方完全一樣……
msvc系列就不行了…… 老老實實的遞歸了……
 
2009-09-24 00:05 | OwnWaterloo

# re: 把二分查找算法寫正確需要注意的地方  回復  更多評論   

@OwnWaterloo
又一次證明了gcc的強大。我猜測gcc的編譯過程中很可能用到了大量的模型匹配,能夠解構復雜的遞歸過程,所以會比M$的效率更高。具體到編譯算法上面我是一點都不知道。
如果是遞歸計算,如果不能很好的優化的話會消耗大量的棧資源,非計算目的的遞歸(我舉不出合適的例子來)應該是無所謂的,gcc很可能也沒有辦法優化。
函數式編程應該是最合適于遞歸運算的。
ps,到這個地方已經徹底跑題了,呵呵。
2009-09-24 02:59 | 唐僧

# re: 把二分查找算法寫正確需要注意的地方  回復  更多評論   

@唐僧
gcc確實牛逼…… 怎么會有這種怪物……
對C/C++新標準支持很快……
compiler collection... 不知道它對非C/C++語言支持得怎樣。
還支持這么多平臺……


如果函數f返回前的最后一個步驟是調用另一個函數g,也就說g返回f后,f確實無事可做,再返回f的調用者;
那么,從理論上說,總是可以優化為:被調用函數g不再返回調用函數f,而直接返回f的調用者。
這樣就可以在g中"復用"f的所使用棧資源。
這被稱為尾調用。
http://en.wikipedia.org/wiki/Tail_call

如果尾調用恰好是調用的自身,就是尾遞歸(調用)。連使用的參數數量都是相同的…… 應該說是比較容易優化的。
并且,如果能將遞歸轉換為尾遞歸形式, 通常"手動"轉化為迭代形式就很簡單了……


上面的遞歸代碼符合尾調用。 我只是想驗證一下是否真的會被編譯器優化。
其實我預想是不行的,結果…… 還真的可以……
這樣就有了一個理由:如果出于演示的目的,如果遞歸比迭代形式更優美,就不提供迭代形式了 —— 轉迭代留作練習、或者換gcc ~_~
2009-09-24 03:47 | OwnWaterloo

# re: 把二分查找算法寫正確需要注意的地方  回復  更多評論   

@OwnWaterloo
你真是夜貓子啊……
Knuth在書里給出了匯編的二分程序,你這么一說我又去看,估計就是gcc優化過的樣子。因為是人寫的程序,所以不存在call/ret,但是je/jne正好實現的是return condition ? recursion(para1) : recursion(para2) 的功能,區別僅僅在于對人來說跳轉的地址是已知的,而對于計算機來說需要棧空間來臨時存儲。書里的匯編代碼是基于Knuth的MIX構架的,不過跟x86的差不多,就是看著麻煩。
突然覺得那個尾調用異常強大,是不是理論上就可以支持無限深度的遞歸調用了?我印象中看編譯原理的時候說call命令的結果是copy返回地址和局部變量到棧空間,那么共用參數的尾遞歸確實非常容易優化,無限深度就很容易被轉成遞推表達式了,呵呵。
2009-09-24 05:18 | 唐僧

# re: 把二分查找算法寫正確需要注意的地方  回復  更多評論   

@唐僧
高爺爺還用匯編實現算法的……
他別那么牛好不好……
 
我貼一下上面的c代碼用gcc生成的匯編代碼吧,不過是intel格式的,因為at&t格式的匯編我看不懂……
.globl _binary_search
    .def    _binary_search;    .scl    
2;    .type    32;    .endef
                               
_binary_search:               
    push    ebp                    
    mov    ebp, esp
    mov    edx, DWORD PTR [ebp
+16]     ;edx = lower
    push    esi                    
    mov    ecx, DWORD PTR [ebp
+20]     ;ecx = upper
    mov    esi, DWORD PTR [ebp
+8]      ;esi = keys
    push    ebx                     
    mov    ebx, DWORD PTR [ebp
+12]     ;ebx = target
    .p2align 
4,,15
L8:
    cmp    edx, ecx                    ;
if (lower!=upper)
    je    L2
L10:
    mov    eax, ecx                    ;middle 
= eax = ecx = upper
    sub    eax, edx                    ;eax 
-= edx, eax = upper-lower
    shr    eax                         ;eax 
/= 2
    add    eax, edx                    ;eax 
+= edx, eax = lower + (upper-lower)/2u
    cmp    DWORD PTR [esi
+eax*4], ebx  ;if (keys[middle] < target)
    jge    L3
    lea    edx, [eax
+1]                ;lower(edx) = middle(eax) + 1
    cmp    edx, ecx                    ;
if ( lower!=upper)
    jne    L10                         ;(keys,target,middle
+1,upper)
L2:
    pop    ebx
    mov    eax, edx                    ;
return lower
    pop    esi
    pop    ebp
    ret
    .p2align 
4,,7
L3:
    mov    ecx, eax                    ;upper(ecx)
=middle
    jmp    L8                          ;f(keys,targer,lower,middle)

迭代生成的匯編代碼僅僅是標號取了不同的名字而已……
 
 
理論上是的, 因為理論上也可以轉為迭代的吧。
實際上,寫出為尾遞歸形式就是需要引入一些額外參數作為計算時的變量。
只要能寫出尾遞歸形式,手動轉迭代都不難了。
 
比如:
int factorial(int x) { return x>2? x*factorial(x-1): 1; }

不是一個尾遞歸, 因為factorial中對factorial調用后,需要取得結果并與x相乘才能返回。
 
轉化為尾遞歸形式,需要引入一個額外參數:
int factorial_tail_recursion(int x,int acc) {
    
return x>2? factorial_tail_recursion(x-1,acc*x): acc;
}

int factorial(int x) { return factorial_tail_recursion(x,1); }

 

而factorial_tail_recursion“手動”轉迭代都不是難事了:
int factorial_iteration(int x,int acc) {
    
while (x>2)
        acc 
*=x , --x;
    
return acc;
}
int factorial(int x) { return factorial_tail_recursion(x,1); }

再把2個函數合二為一, 始終以1作為累積的初始值:
int factorial_iteration_final(int x) {
    
int acc = 1;
    
while (x>2)
        acc 
*=x , --x;
    
return acc;
}

 
還是比較形式化的。 證明估計要留給vczh來做了。
2009-09-26 05:01 | OwnWaterloo

# re: 把二分查找算法寫正確需要注意的地方  回復  更多評論   

建議參考這篇文章,http://blog.csdn.net/qiuqinjun/article/details/8976897,理解了循環不變式的話,遠都不用怕寫二分。
2014-01-28 09:04 | raywill
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            在线视频日韩| 妖精视频成人观看www| 久久精品国产免费| 在线不卡中文字幕| 欧美黑人国产人伦爽爽爽| 免费黄网站欧美| 99在线精品观看| 亚洲视频在线观看视频| 国产一区二区三区电影在线观看 | 国产亚洲欧美日韩一区二区| 蜜臀久久久99精品久久久久久| 久久青草福利网站| 夜夜嗨av一区二区三区 | 久久青草欧美一区二区三区| 久久久成人网| 亚洲天堂男人| 久久精品在线播放| 亚洲午夜免费视频| 久久久www免费人成黑人精品 | 国产精品亚洲综合天堂夜夜| 久久综合久久美利坚合众国| 欧美精品一级| 久久香蕉国产线看观看网| 欧美精品 日韩| 久久精品最新地址| 欧美精品综合| 久久久久久有精品国产| 亚洲激情av在线| 日韩视频国产视频| 亚洲资源av| 亚洲精品视频在线播放| 欧美诱惑福利视频| 亚洲视频成人| 老色鬼精品视频在线观看播放| 亚洲一区二区动漫| 欧美激情亚洲| 久热精品视频在线观看| 国产精品久久久久久久久| 欧美国产一区视频在线观看| 国产日韩一区二区三区| 一区二区日韩| 一本大道久久a久久综合婷婷| 久久精品欧美日韩精品| 欧美专区日韩视频| 国产精品乱码人人做人人爱| 亚洲美女网站| 亚洲精品网址在线观看| 噜噜噜久久亚洲精品国产品小说| 久久精品免费播放| 国产热re99久久6国产精品| 日韩视频免费| 亚洲午夜电影网| 欧美女同在线视频| 91久久久亚洲精品| 日韩午夜在线观看视频| 欧美成人乱码一区二区三区| 欧美岛国在线观看| 亚洲国产成人精品久久| 久久夜色精品一区| 欧美国内亚洲| 99riav久久精品riav| 欧美精品久久天天躁| 亚洲黄色有码视频| 99成人在线| 欧美色精品在线视频| av成人毛片| 亚洲欧美日韩在线观看a三区 | 欧美大秀在线观看| 亚洲人线精品午夜| 夜夜嗨av一区二区三区四季av| 欧美激情一区二区三区在线视频观看| 欧美激情免费观看| 一本色道久久综合亚洲精品婷婷| 欧美激情导航| 中国亚洲黄色| 久久久久九九九九| 亚洲国产另类 国产精品国产免费| 久久免费国产精品| 亚洲经典一区| 性色av一区二区怡红| 国产亚洲欧美在线| 久久野战av| 日韩视频―中文字幕| 欧美一区二区三区男人的天堂| 国产一区在线看| 欧美国产1区2区| 亚洲性线免费观看视频成熟| 久久夜色精品国产噜噜av| 91久久在线播放| 国产精品久久久久久久午夜| 欧美专区在线| 日韩视频一区二区在线观看| 欧美制服丝袜第一页| 亚洲片在线资源| 国产精品毛片a∨一区二区三区|国| 午夜精品在线| 亚洲一区二区三区四区五区黄| 国产欧美一区二区精品忘忧草| 久久综合给合久久狠狠狠97色69| 日韩一区二区高清| 久久综合图片| 亚洲一区二区免费在线| 在线日本成人| 国产精品盗摄久久久| 久久综合色影院| 亚洲欧美日韩精品在线| 欧美成人免费网| 久久成年人视频| 一区二区三区日韩精品| 在线观看成人一级片| 国产精品日韩一区二区| 欧美精品成人一区二区在线观看| 性色一区二区| 国产精品99久久久久久久久| 欧美二区不卡| 另类人畜视频在线| 久久se精品一区精品二区| 亚洲视频图片小说| 亚洲精品视频二区| 亚洲国产天堂久久综合| 国产一区二区三区av电影| 国产精品伦一区| 欧美日韩国产成人在线免费| 欧美chengren| 老司机一区二区三区| 性色av香蕉一区二区| 亚洲综合好骚| 亚洲在线一区二区| 亚洲午夜激情在线| 这里是久久伊人| 一区二区三区免费在线观看| 亚洲日本免费电影| 亚洲人成人77777线观看| 欧美国产高潮xxxx1819| 女人香蕉久久**毛片精品| 久久久在线视频| 久久久久国内| 美女精品一区| 欧美国产一区在线| 欧美二区视频| 91久久国产综合久久91精品网站| 欧美高清在线一区二区| 欧美二区在线播放| 亚洲激情不卡| av成人国产| 亚洲免费在线观看| 欧美中文在线视频| 久久亚洲精品伦理| 欧美成人精品一区二区| 欧美美女喷水视频| 国产精品va在线播放| 国产日产亚洲精品| 影音先锋另类| 日韩视频在线免费观看| 亚洲视频一区| 欧美一区二区在线播放| 久久综合久久综合这里只有精品 | 午夜精品久久久| 久久国产精品久久久久久电车 | 亚洲影视在线播放| 欧美一区二区三区在| 免费不卡在线视频| 亚洲精品综合| 午夜日韩激情| 欧美国产日韩二区| 国产精品乱人伦中文| 极品日韩av| 亚洲视频高清| 久久美女性网| 亚洲精品国久久99热| 亚洲欧美日本国产有色| 另类尿喷潮videofree| 欧美三级特黄| 伊人久久av导航| 香蕉尹人综合在线观看| 麻豆精品91| 国产精品日韩精品| 亚洲精品一区二区三区樱花| 亚洲欧美在线aaa| 亚洲国产精品传媒在线观看 | 麻豆精品精华液| 国产精品色午夜在线观看| 在线观看欧美日韩| 香蕉久久一区二区不卡无毒影院| 欧美成黄导航| 亚洲欧美网站| 欧美日韩国产91| 在线不卡a资源高清| 亚洲欧美综合精品久久成人| 欧美激情一二区| 香港久久久电影| 欧美特黄a级高清免费大片a级| 在线观看成人av| 欧美在线|欧美| 在线视频欧美精品| 欧美国产日韩一二三区| 国内精品伊人久久久久av一坑| 亚洲小视频在线| 亚洲日本激情| 欧美成人黑人xx视频免费观看|