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

那誰的技術博客

感興趣領域:高性能服務器編程,存儲,算法,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 那誰 閱讀(11194) 評論(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>
            中文在线一区| 一本到12不卡视频在线dvd| 先锋影音网一区二区| 国产精品va在线| 性欧美暴力猛交另类hd| 亚洲欧美综合国产精品一区| 国产人成精品一区二区三| 久久精品99国产精品日本| 久久亚洲欧美国产精品乐播| 亚洲人成在线播放| 一本久道久久综合狠狠爱| 国产精品视频1区| 美日韩精品视频免费看| 欧美人与禽猛交乱配| 销魂美女一区二区三区视频在线| 欧美在线观看视频一区二区| 亚洲欧洲在线播放| 久久久人成影片一区二区三区 | 亚洲精品一区二区三| 欧美三级电影精品| 久久久久天天天天| 欧美精品一区二区三区在线看午夜 | 亚洲国产日日夜夜| 国产精品国产三级国产普通话蜜臀| 亚洲欧美日韩一区在线| 久久久成人精品| 亚洲一线二线三线久久久| 欧美中文字幕视频在线观看| 亚洲另类黄色| 久久精选视频| 亚洲欧美卡通另类91av| 老司机精品福利视频| 午夜激情亚洲| 欧美激情四色 | 亚洲国产精品小视频| 亚洲午夜在线| 99re视频这里只有精品| 久久久国产精品一区二区中文| 一区二区三区欧美亚洲| 久热精品在线| 久久久久一区二区| 国产精品www色诱视频| 欧美激情一二三区| 国产一区二区在线观看免费播放| 亚洲精品综合久久中文字幕| 亚洲美女av在线播放| 蜜臀91精品一区二区三区| 中文亚洲免费| 卡通动漫国产精品| 欧美激情欧美狂野欧美精品| 老司机午夜精品视频| 国产色产综合色产在线视频| 日韩一级免费| 99精品免费视频| 欧美国内亚洲| 亚洲第一黄色| 亚洲国产欧美在线人成| 久久久蜜桃精品| 久久夜色精品一区| 国内精品亚洲| 久久久亚洲午夜电影| 久久中文字幕一区二区三区| 国产一区二区三区无遮挡| 亚洲欧美高清| 久久久久久国产精品mv| 欧美成人首页| 亚洲福利视频二区| 性做久久久久久| 久久精品色图| 亚洲高清自拍| 欧美 日韩 国产在线| 亚洲成人在线视频播放 | 欧美亚一区二区| 中文精品视频| 性色av一区二区三区在线观看| 国产精品免费网站在线观看| 亚洲免费在线视频| 狂野欧美性猛交xxxx巴西| 亚洲国产成人一区| 欧美精品一二三| 一区二区三区免费网站| 欧美亚洲一区二区三区| 国产自产精品| 欧美bbbxxxxx| 亚洲午夜高清视频| 久久久无码精品亚洲日韩按摩| 亚洲第一天堂av| 欧美日韩视频不卡| 欧美亚洲一区在线| 欧美国产激情二区三区| 亚洲性线免费观看视频成熟| 国产麻豆综合| 噜噜噜躁狠狠躁狠狠精品视频| 亚洲欧洲在线观看| 欧美一区激情| 亚洲精品美女久久7777777| 欧美日韩亚洲一区二区三区在线观看 | 亚洲美女电影在线| 欧美中文字幕久久| 亚洲日韩欧美视频一区| 国产精品久久久久久久久免费桃花| 欧美一区二区三区久久精品| 亚洲国产精品激情在线观看| 亚洲欧美中文在线视频| 亚洲国产精彩中文乱码av在线播放| 欧美日韩国产一区二区三区地区| 午夜日韩电影| 亚洲精品一区二区三区四区高清| 欧美亚洲一级片| 日韩午夜在线观看视频| 国产一级久久| 国产精品二区在线| 欧美顶级大胆免费视频| 欧美在线电影| 亚洲一区免费| 日韩视频在线一区二区三区| 欧美电影免费观看高清| 久久精品视频一| 亚洲女人av| 一区二区三区欧美亚洲| 亚洲福利专区| 精品999在线播放| 国产欧美日韩免费| 国产精品高潮在线| 欧美日韩国产精品| 免费毛片一区二区三区久久久| 欧美一区二区视频在线| 亚洲一二区在线| 99视频在线观看一区三区| 亚洲国产成人久久综合| 欧美成人a视频| 久久综合久久美利坚合众国| 久久精品国产久精国产爱| 香蕉视频成人在线观看| 在线 亚洲欧美在线综合一区| 亚洲国产精品一区二区尤物区| 久久久久成人精品| 久久国产直播| 久久精品在线播放| 久久精品72免费观看| 午夜免费在线观看精品视频| 亚洲一区欧美一区| 亚洲一区国产| 新片速递亚洲合集欧美合集| 亚洲综合国产| 欧美亚洲综合另类| 久久精品av麻豆的观看方式| 欧美中文字幕精品| 猫咪成人在线观看| 亚洲国产91精品在线观看| 亚洲国产高清在线| 亚洲精品日韩久久| 中文欧美字幕免费| 欧美一区网站| 久久久亚洲精品一区二区三区| 美女成人午夜| 欧美日韩精品免费观看视一区二区| 欧美日韩一级黄| 欧美日韩你懂的| 国产精品视频导航| 激情综合五月天| 亚洲剧情一区二区| 午夜精品在线| 欧美va亚洲va日韩∨a综合色| 亚洲国产日韩一级| 亚洲一级黄色片| 久久久久国色av免费观看性色| 欧美.日韩.国产.一区.二区| 欧美日韩精品免费观看视频完整 | 久久久人成影片一区二区三区| 欧美电影免费| 国产精品区一区二区三| 激情视频一区二区| 一本色道久久综合亚洲精品高清| 性色av一区二区三区| 欧美成人午夜激情在线| 一本久道久久综合中文字幕| 久久精品一区中文字幕| 欧美日韩精品一本二本三本| 国产欧美欧美| 一本色道久久综合| 久久久噜噜噜| 中日韩午夜理伦电影免费| 久久人人看视频| 国产精品日韩欧美综合| 亚洲精一区二区三区| 久久久久久久久久久久久久一区 | 亚洲视频久久| 欧美www视频在线观看| 国产农村妇女精品一二区| 亚洲日本成人| 久久久亚洲成人| 亚洲一区二区三区四区五区午夜| 美国十次成人| 好看的av在线不卡观看| 亚洲欧美制服另类日韩| 亚洲精品视频在线看| 久久亚洲精品网站| 国产亚洲精品v| 午夜在线a亚洲v天堂网2018|