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

那誰(shuí)的技術(shù)博客

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

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

今天再次解決一個(gè)需要使用二分查找的問(wèn)題,再一次的,我又沒(méi)有一次過(guò)寫對(duì).(為什么我說(shuō)"又"?)

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

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

我今早寫下的錯(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í)際上,如果使用測(cè)試用例來(lái)測(cè)試,這個(gè)算法并不是在所有情況下都會(huì)出錯(cuò)的,還是有時(shí)可以得到正確的結(jié)果的.但是,你能看出來(lái)它錯(cuò)在哪兒?jiǎn)?

在這里,循環(huán)的開始處,把循環(huán)遍歷的序列區(qū)間是這樣的:
left =0, right = n;
while (left < right)
{
    
// 循環(huán)體
}
也就是說(shuō),這是一個(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ū)間算法,可以對(duì)比一下差異.
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)條件來(lái)看,這個(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).

因此,要將二分查找算法寫對(duì),其實(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 那誰(shuí) 閱讀(11194) 評(píng)論(29)  編輯 收藏 引用 所屬分類: 算法與數(shù)據(jù)結(jié)構(gòu)

評(píng)論

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

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

仔細(xì)推敲邊界的人不多。 大多都是隨手寫寫, 最多加幾個(gè)測(cè)試數(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ù)  更多評(píng)論   

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

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

@那誰(shuí)
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)系
等等…… 都挺有意思的……
不過(guò)最近有其他事要忙…… 只好終止了……

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

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

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

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

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

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

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

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


two-way就能有明確的取向啊。
對(duì)區(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。

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

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

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

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

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

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

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


2009-09-22 15:13 | 那誰(shuí)

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

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

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

2009-09-22 15:21 | OwnWaterloo

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

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

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

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

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

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

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


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


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

2009-09-22 17:53 | OwnWaterloo

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

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

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

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

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

@唐僧
謝謝~~

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

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

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

2009-09-22 20:43 | OwnWaterloo

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

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

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

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

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

The art of computer programming …… 一直沒(méi)下決心去啃……

這個(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無(wú)序, 但a[1] - a[3]升序。
binary_search(keys=a, target=5, lower=1,upper=3 );


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

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

2009-09-23 05:51 | OwnWaterloo

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

@唐僧

哦 ……

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ù)  更多評(píng)論   

@唐僧
再回一貼 ……

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è)庫(kù)都要求在main中這么初始化一次, main會(huì)受不了的……

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

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

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

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

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

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

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

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

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

@OwnWaterloo
我對(duì)于TMP沒(méi)有研究,呵呵,就等c++0x了。

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

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

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

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

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

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

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

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

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


如果函數(shù)f返回前的最后一個(gè)步驟是調(diào)用另一個(gè)函數(shù)g,也就說(shuō)g返回f后,f確實(shí)無(wú)事可做,再返回f的調(diào)用者;
那么,從理論上說(shuō),總是可以優(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)該說(shuō)是比較容易優(yōu)化的。
并且,如果能將遞歸轉(zhuǎn)換為尾遞歸形式, 通常"手動(dòng)"轉(zhuǎn)化為迭代形式就很簡(jiǎ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ù)  更多評(píng)論   

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

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

@唐僧
高爺爺還用匯編實(shí)現(xiàn)算法的……
他別那么牛好不好……
 
我貼一下上面的c代碼用gcc生成的匯編代碼吧,不過(guò)是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)號(hà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中對(duì)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來(lái)做了。
2009-09-26 05:01 | OwnWaterloo

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

建議參考這篇文章,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>
            亚洲伊人网站| 亚洲美女网站| 久久在精品线影院精品国产| 午夜精品久久久久久久99樱桃 | 久久久久久久久岛国免费| 国产专区欧美专区| 美女图片一区二区| 欧美黄网免费在线观看| 一区二区三区精品视频在线观看 | 免费在线观看精品| 欧美电影电视剧在线观看| 亚洲无限av看| 欧美在线观看天堂一区二区三区 | 国产在线精品二区| 欧美激情视频在线播放| 欧美日本网站| 久久久国产亚洲精品| 欧美+亚洲+精品+三区| 久久se精品一区二区| 欧美视频中文字幕在线| 亚洲一区二区三区在线| 午夜精品免费在线| 红杏aⅴ成人免费视频| 最新国产精品拍自在线播放| 欧美日韩另类国产亚洲欧美一级| 欧美一区=区| 免费不卡在线视频| 亚洲欧美一区二区视频| 久久国产日韩| 亚洲一区二区三区视频| 久久久噜噜噜久久狠狠50岁| 亚洲午夜小视频| 久久亚洲捆绑美女| 午夜精品一区二区三区四区 | 欧美激情一区二区三区全黄| 亚洲一区二区四区| 久久夜色精品国产噜噜av| 亚洲欧美综合网| 欧美伦理91| 欧美成人中文| 好看的日韩视频| 亚洲影音先锋| 亚洲永久免费视频| 欧美精品在欧美一区二区少妇| 久久琪琪电影院| 国产精品推荐精品| 99在线精品观看| 亚洲免费播放| 免费短视频成人日韩| 久久亚洲视频| 黑丝一区二区| 久久久www成人免费毛片麻豆| 欧美一区二区三区久久精品| 国产精品s色| 一区二区三区视频观看| 在线亚洲观看| 欧美色网在线| 一二三区精品福利视频| 日韩午夜在线电影| 欧美激情黄色片| 欧美激情国产日韩精品一区18| 亚洲成人在线| 嫩草伊人久久精品少妇av杨幂| 毛片基地黄久久久久久天堂| 国模吧视频一区| 久久精品一区二区三区不卡牛牛 | 久久激情五月婷婷| 国产欧美日韩免费看aⅴ视频| 亚洲在线视频| 久久精品人人爽| 在线观看精品视频| 狼人天天伊人久久| 亚洲国产美国国产综合一区二区| 最新中文字幕一区二区三区| 一区二区三区国产在线| 欧美人与禽性xxxxx杂性| 最新成人av网站| 亚洲性线免费观看视频成熟| 国产精品久久久久7777婷婷| 亚洲欧美日韩国产| 久久婷婷人人澡人人喊人人爽| 在线欧美日韩国产| 欧美日韩成人精品| 亚洲欧美日产图| 麻豆国产精品777777在线| 亚洲三级影院| 国产精品www.| 久久婷婷亚洲| 一区二区三区视频在线| 久久精品一区二区三区不卡牛牛| 在线观看不卡av| 欧美日韩999| 欧美一区精品| 亚洲毛片播放| 久久美女性网| 亚洲午夜在线观看| 国内精品一区二区| 欧美日韩亚洲一区| 久久精品一区二区三区中文字幕| 亚洲日本成人| 久久久久这里只有精品| 中文亚洲字幕| 亚洲缚视频在线观看| 国产精品久久久久一区二区三区共| 久久国产直播| 亚洲一区3d动漫同人无遮挡| 久久亚洲精品一区二区| 亚洲无限乱码一二三四麻| 在线观看国产日韩| 国产精品亚洲产品| 欧美日韩国产小视频| 久久亚洲一区二区三区四区| 亚洲一区二区三区四区在线观看 | 久久久无码精品亚洲日韩按摩| 亚洲美女诱惑| 欧美大秀在线观看| 久久久国产精品一区二区中文 | 久久亚洲国产精品一区二区 | 亚洲三级免费| 好看不卡的中文字幕| 国产精品入口麻豆原神| 欧美麻豆久久久久久中文| 久久久综合视频| 欧美一二三视频| 亚洲制服丝袜在线| aaa亚洲精品一二三区| 91久久久在线| 欧美激情乱人伦| 蘑菇福利视频一区播放| 久久五月婷婷丁香社区| 久久www免费人成看片高清| 亚洲综合首页| 亚洲欧美日本日韩| 亚洲一区精彩视频| 亚洲一区国产精品| 亚洲欧美国产精品专区久久| 亚洲一区二区三区免费视频| 一区二区三区国产盗摄| 一区二区三区欧美视频| 久久综合一区二区三区| 欧美3dxxxxhd| 久久阴道视频| 另类欧美日韩国产在线| 久久久欧美一区二区| 久久久久这里只有精品| 老司机午夜精品| 老司机久久99久久精品播放免费| 久久综合导航| 欧美激情精品久久久久久久变态 | 一本一本久久a久久精品牛牛影视| 日韩一区二区免费高清| 日韩午夜三级在线| 亚洲午夜久久久| 欧美一区二区观看视频| 久久久久国产精品人| 麻豆av福利av久久av| 欧美精品免费播放| 欧美无砖砖区免费| 国产午夜精品在线观看| 一区二区在线不卡| 亚洲免费观看在线观看| 亚洲欧美成人在线| 久久久久一区二区| 亚洲欧洲一区二区三区| 亚洲视频在线二区| 久久高清免费观看| 欧美电影在线观看| 国产精品亚洲成人| 亚洲国产成人91精品| 亚洲视频一二| 久久婷婷影院| 一区二区高清视频| 久久精品视频99| 欧美日韩国产综合视频在线| 国产美女诱惑一区二区| 最近看过的日韩成人| 欧美一级视频精品观看| 欧美激情一区二区| 羞羞漫画18久久大片| 欧美国产先锋| 国产亚洲欧美另类中文| 99在线热播精品免费| 久久久久久久久伊人| 亚洲精品一区在线| 久久久久在线| 国产精品一区二区在线观看不卡 | 亚洲欧美激情视频在线观看一区二区三区| 欧美在线日韩在线| 亚洲毛片在线看| 玖玖玖国产精品| 国产视频在线观看一区二区| 99视频精品在线| 久久综合伊人77777麻豆| 亚洲午夜激情网页| 欧美日本国产在线| 亚洲精品乱码久久久久久久久 | 久久国产主播| 亚洲一区二区av电影| 欧美日韩亚洲高清一区二区| 亚洲激情婷婷|