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

那誰的技術(shù)博客

感興趣領(lǐng)域:高性能服務(wù)器編程,存儲(chǔ),算法,Linux內(nèi)核
隨筆 - 210, 文章 - 0, 評論 - 1183, 引用 - 0
數(shù)據(jù)加載中……

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

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

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

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

我今早寫下的錯(cuò)誤代碼類似于下面的樣子:
#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;
}

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

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

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

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

因此,這種錯(cuò)誤的寫法并不是在所有的情況下都會(huì)出錯(cuò),有時(shí)還是可以找到正確的結(jié)果的.

這是一種典型的二分查找算法寫錯(cuò)的情況,循環(huán)體是左閉右開區(qū)間,而循環(huán)體內(nèi)部卻是采用左閉右閉區(qū)間的算法進(jìn)行操作.
下面給出的兩種正確的算法,算法search是左閉右閉區(qū)間算法,而算法search2是左閉右開區(qū)間算法,可以對比一下差異.
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;
}

下面再給出另一種典型的錯(cuò)誤的二分查找算法,當(dāng)查找的元素不在序列內(nèi)時(shí),它可能造成程序的死循環(huán).
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;
}
為什么會(huì)造成死循環(huán)?

從循環(huán)條件來看,這個(gè)算法的操作區(qū)間是左閉右閉區(qū)間的,因此當(dāng)array[middle] > v時(shí),v如果存在的話應(yīng)該在[left, middle- 1]中,因此此時(shí)right應(yīng)該是middle - 1,而不是middle;類似的,當(dāng)array[middle] < v時(shí),下一次操作的區(qū)間應(yīng)該是[middle + 1, right]中.而當(dāng)元素不存在這個(gè)序列中時(shí),算法在一個(gè)錯(cuò)誤的區(qū)間中循環(huán),但是又不能終止循環(huán),于是就造成了死循環(huán).

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



posted on 2009-09-21 23:46 那誰 閱讀(11220) 評論(29)  編輯 收藏 引用 所屬分類: 算法與數(shù)據(jù)結(jié)構(gòu)

評論

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

因?yàn)槎植檎业乃枷牒芎唵危?很多人稍微看看就開始編碼了, 沒有考慮:
1. 每次遞歸中,區(qū)間如何劃分
2. 遞歸的邊界有哪些,是否能達(dá)到
3. 查找的值存在多個(gè)時(shí), 將取得哪一個(gè)

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

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

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

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

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


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

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

遞歸邊界是區(qū)間只含一個(gè)元素: [lower,lower]

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


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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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


2009-09-22 15:13 | 那誰

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

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

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

2009-09-22 15:21 | OwnWaterloo

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

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

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

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

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

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

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


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


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

2009-09-22 17:53 | OwnWaterloo

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

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

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

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

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

@唐僧
謝謝~~

我想到一個(gè)證明3-way不可能實(shí)現(xiàn)確定取向的方法。 其實(shí)非常簡單……

因?yàn)橐粋€(gè)binary-search算法,必須對所有輸入數(shù)據(jù)都有確定的取向,才能說該算法有確定取向。
故證明某個(gè)binary-search算法不具有確定取向只要構(gòu)造出一個(gè)反例,即可……

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

2009-09-22 20:43 | OwnWaterloo

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

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

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

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

@唐僧
4點(diǎn)58分的回復(fù)……

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

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


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

打算將數(shù)據(jù)結(jié)構(gòu)重新學(xué)一遍…… 透徹理解一下……
以前學(xué)的那叫啥…… 所以嘛, 只能看著室友拿金牌-_-。

2009-09-23 05:51 | OwnWaterloo

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

@唐僧

哦 ……

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

快天亮了, 頭有點(diǎn)暈……
2009-09-23 05:54 | OwnWaterloo

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

@唐僧
再回一貼 ……

Uniform binary search中需要的那個(gè)table —— delta, 好像可以用template meta programming在編譯時(shí)計(jì)算完畢 ……
C++太強(qiáng)大了~_~

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

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

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

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

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

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

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

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

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

@那誰
那個(gè)凌晨的時(shí)間是我的夜晚,我是GMT+1。其他的回復(fù)都是在課間,我現(xiàn)在這課上的,一天7個(gè)小時(shí),所以幾乎沒有白天的回復(fù)。

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

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

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

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

gcc也能將為遞歸優(yōu)化為迭代。
下面是一個(gè)轉(zhuǎn)化為迭代的版本:
iteration

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

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

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

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

@唐僧
gcc確實(shí)牛逼…… 怎么會(huì)有這種怪物……
對C/C++新標(biāo)準(zhǔn)支持很快……
compiler collection... 不知道它對非C/C++語言支持得怎樣。
還支持這么多平臺(tái)……


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

如果尾調(diào)用恰好是調(diào)用的自身,就是尾遞歸(調(diào)用)。連使用的參數(shù)數(shù)量都是相同的…… 應(yīng)該說是比較容易優(yōu)化的。
并且,如果能將遞歸轉(zhuǎn)換為尾遞歸形式, 通常"手動(dòng)"轉(zhuǎn)化為迭代形式就很簡單了……


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

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

@OwnWaterloo
你真是夜貓子啊……
Knuth在書里給出了匯編的二分程序,你這么一說我又去看,估計(jì)就是gcc優(yōu)化過的樣子。因?yàn)槭侨藢懙某绦颍圆淮嬖赾all/ret,但是je/jne正好實(shí)現(xiàn)的是return condition ? recursion(para1) : recursion(para2) 的功能,區(qū)別僅僅在于對人來說跳轉(zhuǎn)的地址是已知的,而對于計(jì)算機(jī)來說需要棧空間來臨時(shí)存儲(chǔ)。書里的匯編代碼是基于Knuth的MIX構(gòu)架的,不過跟x86的差不多,就是看著麻煩。
突然覺得那個(gè)尾調(diào)用異常強(qiáng)大,是不是理論上就可以支持無限深度的遞歸調(diào)用了?我印象中看編譯原理的時(shí)候說call命令的結(jié)果是copy返回地址和局部變量到棧空間,那么共用參數(shù)的尾遞歸確實(shí)非常容易優(yōu)化,無限深度就很容易被轉(zhuǎn)成遞推表達(dá)式了,呵呵。
2009-09-24 05:18 | 唐僧

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

@唐僧
高爺爺還用匯編實(shí)現(xiàn)算法的……
他別那么牛好不好……
 
我貼一下上面的c代碼用gcc生成的匯編代碼吧,不過是intel格式的,因?yàn)閍t&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)

迭代生成的匯編代碼僅僅是標(biāo)號取了不同的名字而已……
 
 
理論上是的, 因?yàn)槔碚撋弦部梢赞D(zhuǎn)為迭代的吧。
實(shí)際上,寫出為尾遞歸形式就是需要引入一些額外參數(shù)作為計(jì)算時(shí)的變量。
只要能寫出尾遞歸形式,手動(dòng)轉(zhuǎn)迭代都不難了。
 
比如:
int factorial(int x) { return x>2? x*factorial(x-1): 1; }

不是一個(gè)尾遞歸, 因?yàn)閒actorial中對factorial調(diào)用后,需要取得結(jié)果并與x相乘才能返回。
 
轉(zhuǎn)化為尾遞歸形式,需要引入一個(gè)額外參數(shù):
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“手動(dòng)”轉(zhuǎn)迭代都不是難事了:
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個(gè)函數(shù)合二為一, 始終以1作為累積的初始值:
int factorial_iteration_final(int x) {
    
int acc = 1;
    
while (x>2)
        acc 
*=x , --x;
    
return acc;
}

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

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

建議參考這篇文章,http://blog.csdn.net/qiuqinjun/article/details/8976897,理解了循環(huán)不變式的話,遠(yuǎn)都不用怕寫二分。
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>
            久久综合婷婷| 亚洲少妇诱惑| 欧美三级日韩三级国产三级| 麻豆国产精品一区二区三区| 久久久视频精品| 免费成年人欧美视频| 久久精品中文字幕免费mv| 亚洲视频在线观看三级| 国产精品视频导航| 国产网站欧美日韩免费精品在线观看| 国产精品电影在线观看| 国产欧美在线观看一区| 在线成人性视频| 9人人澡人人爽人人精品| 亚洲欧美一区在线| 久久资源在线| 99re66热这里只有精品3直播| 亚洲午夜精品久久| 久久久久久国产精品一区| 欧美久久久久久久久久| 国产欧美日韩视频| 亚洲免费av片| 久久精品盗摄| 亚洲欧洲一区二区在线观看| 99综合在线| 久久夜色精品国产| 国产精品日韩二区| 日韩亚洲欧美精品| 久久只有精品| 亚洲自拍偷拍视频| 欧美黄色aa电影| 韩国精品主播一区二区在线观看| 一本色道久久综合亚洲91| 久久精品一区二区三区四区| 亚洲美女淫视频| 免费亚洲电影在线| 国内精品一区二区三区| 亚洲在线一区二区| 亚洲国产日韩欧美| 久久久久久久一区| 国产模特精品视频久久久久| 99视频精品全国免费| 欧美电影免费观看高清| 午夜亚洲一区| 国产精品色婷婷| 一区二区三区视频在线| 亚洲第一久久影院| 久久一区二区三区av| 国产一区二区三区直播精品电影| 亚洲一区二区三区四区五区午夜| 亚洲第一偷拍| 久久一日本道色综合久久| 国产日韩精品在线播放| 午夜精品一区二区三区电影天堂| 亚洲肉体裸体xxxx137| 久久久久久久久伊人| 韩国欧美国产1区| 狂野欧美性猛交xxxx巴西| 欧美影院久久久| 国产日韩成人精品| 久久国产婷婷国产香蕉| 午夜精品久久久久久久蜜桃app| 亚洲一区二区三区高清 | 欧美不卡视频一区发布| 亚洲欧美在线观看| 国产欧美在线看| 久久国产精品久久精品国产| 一区二区精品在线| 国产精品v欧美精品v日本精品动漫| 亚洲免费电影在线观看| 亚洲精品久久久久久一区二区| 欧美精品国产一区| 一区二区三区欧美日韩| 亚洲天堂视频在线观看| 国产精品色在线| 久久久久久黄| 美乳少妇欧美精品| 99re视频这里只有精品| 日韩午夜在线电影| 国产视频精品xxxx| 国产精品一区二区久久久久| 在线亚洲伦理| 中文在线一区| 国产日韩欧美高清| 欧美成人午夜激情| 欧美午夜不卡影院在线观看完整版免费| 亚洲一区欧美| 久久精品日产第一区二区| 亚洲清纯自拍| 亚洲综合色视频| 在线看片成人| 这里只有精品视频在线| 狠狠久久亚洲欧美| 日韩五码在线| 狠狠网亚洲精品| 夜夜嗨av一区二区三区网页| 狠狠色综合色区| 一二三四社区欧美黄| 精品99视频| 亚洲调教视频在线观看| 亚洲国产精品久久| 午夜精品久久久久久99热| 亚洲三级视频| 久久成年人视频| 亚洲性夜色噜噜噜7777| 免费看成人av| 久久久精品国产一区二区三区| 欧美激情欧美激情在线五月| 久久久久久久久综合| 国产精品大全| 亚洲精品国偷自产在线99热| 一区二区在线观看视频| 亚洲免费一在线| 在线亚洲电影| 欧美护士18xxxxhd| 欧美国产精品日韩| 韩国在线一区| 性做久久久久久| 午夜精品久久久| 欧美天堂在线观看| 亚洲精品一区二区在线观看| 在线精品国精品国产尤物884a| 欧美一区亚洲| 欧美自拍偷拍午夜视频| 欧美一区二区三区喷汁尤物| 久久午夜色播影院免费高清| 性欧美8khd高清极品| 欧美日韩国产综合在线| 欧美激情一区二区三区成人| 激情综合亚洲| 欧美在线观看www| 久久精品在这里| 国产一区二区三区直播精品电影 | 欧美有码在线视频| 午夜欧美大片免费观看| 欧美大学生性色视频| 亚洲一区二区三区四区在线观看| 久久精品国产欧美亚洲人人爽 | 欧美一区二区三区免费视频| 免费看av成人| 亚洲欧美日韩国产精品| 欧美激情导航| 黑人操亚洲美女惩罚| 亚洲免费在线| 夜夜嗨av一区二区三区网站四季av | 亚洲欧美亚洲| 欧美日韩综合视频| 99精品福利视频| 亚洲一区网站| 国产婷婷色一区二区三区在线 | 亚洲第一黄色| 夜夜嗨av色一区二区不卡| 欧美精品日韩精品| 99精品国产高清一区二区| 亚洲自拍偷拍福利| 国产欧美一区二区色老头 | 国产精自产拍久久久久久蜜| 香蕉久久a毛片| 欧美不卡在线| 一区二区精品| 国产色综合久久| 免费不卡在线视频| 99热在这里有精品免费| 欧美综合国产| 免费看成人av| 亚洲综合不卡| 国产精品你懂的在线欣赏| 欧美一区二区三区免费在线看 | 欧美日韩亚洲一区二区三区在线| 一本不卡影院| 久久中文在线| 一区二区三区黄色| 国产人成一区二区三区影院| 另类天堂av| 亚洲一区二区三区精品视频| 免费视频最近日韩| 亚洲欧美国产精品va在线观看| 国内伊人久久久久久网站视频 | 久久久久久久久久久久久久一区| 亚洲国产精品国自产拍av秋霞 | 日韩一级黄色片| 久久一综合视频| 夜夜爽99久久国产综合精品女不卡| 国产精品一区久久久| 欧美超级免费视 在线| 亚洲一区三区视频在线观看| 欧美fxxxxxx另类| 欧美一级成年大片在线观看| 日韩一区二区免费高清| 一色屋精品视频在线看| 国产精品日韩专区| 欧美人与性动交a欧美精品| 欧美呦呦网站| 亚洲欧美精品suv| 日韩亚洲视频在线| 亚洲欧洲日本国产| 欧美国产三级| 男同欧美伦乱| 裸体一区二区三区| 欧美中文字幕视频|