• <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>

            那誰的技術(shù)博客

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

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

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

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

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

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

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

            在這里,循環(huán)的開始處,把循環(huán)遍歷的序列區(qū)間是這樣的:
            left =0, right = n;
            while (left < right)
            {
                
            // 循環(huán)體
            }
            也就是說,這是一個左閉右開的區(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條件滿足時, 此時v如果存在的話必然在左閉右開區(qū)間[left, middle)中, 因此,當(dāng)這個條件滿足時, right應(yīng)該為middle, 而在這里, right賦值為middle - 1了, 那么, 就有可能遺漏array[middle - 1] = v的情況.

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

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

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

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

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



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

            評論

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

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

            仔細推敲邊界的人不多。 大多都是隨手寫寫, 最多加幾個測試數(shù)據(jù)。
            區(qū)間劃分, 我只在少數(shù)幾個地方看到是被“二分”, 普遍做法是“三分”。
            少數(shù)幾個地方是《代碼之美》;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個區(qū)間[lower,m] ; [m+1,upper]

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

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


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

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

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

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

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

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

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

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

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


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

            2. 如果取中值 m = (lower+upper+1)/2
            將區(qū)間劃分為[lower,m-1],[m,upper]
            直到區(qū)間只有一個元素:[lower,lower]
            取向就是從upper到lower,第1個小于等于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
            編程珠璣里面確實有一章講解算法正確性的,是以二分查找算法來講解的.
            2009-09-22 15:07 | 那誰

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

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


            2009-09-22 15:13 | 那誰

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

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

            我不知道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,怎么能給列表增加太多負擔(dān)
            2009-09-22 17:33 | 陳梓瀚(vczh)

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

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

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


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


            如果待查找的序列有相等的鍵, 那么查找算法有一定的取向就是一個有用的需求。跟序列是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 這個鏈接是目錄,以二分為例講的,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ì),只是外殼不同罷了。
            如果要搜索的是某個子區(qū)間而不是單一值的話,考慮多值取向就很有必要了。

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

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

            @唐僧
            謝謝~~

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

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

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

            2009-09-22 20:43 | OwnWaterloo

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

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

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

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

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

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

            這個Uniform binary search是不是將除法操作緩存一下?
            其實聰明點的編譯器同樣可以將
            i /= 2u; 優(yōu)化成 shr i
            它使用一個預(yù)先計算好的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的……

            打算將數(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 );

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

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

            @唐僧
            再回一貼 ……

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


            如果函數(shù)f返回前的最后一個步驟是調(diào)用另一個函數(shù)g,也就說g返回f后,f確實無事可做,再返回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)換為尾遞歸形式, 通常"手動"轉(zhuǎn)化為迭代形式就很簡單了……


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

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

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

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

            @唐僧
            高爺爺還用匯編實現(xiàn)算法的……
            他別那么牛好不好……
             
            我貼一下上面的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)

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

            不是一個尾遞歸, 因為factorial中對factorial調(diào)用后,需要取得結(jié)果并與x相乘才能返回。
             
            轉(zhuǎn)化為尾遞歸形式,需要引入一個額外參數(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“手動”轉(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個函數(shù)合二為一, 始終以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: 把二分查找算法寫正確需要注意的地方  回復(fù)  更多評論   

            建議參考這篇文章,http://blog.csdn.net/qiuqinjun/article/details/8976897,理解了循環(huán)不變式的話,遠都不用怕寫二分。
            2014-01-28 09:04 | raywill
            久久综合狠狠色综合伊人| 日本久久久精品中文字幕| 伊人久久大香线蕉精品| 亚洲色大成网站www久久九| 久久久久久久久久免免费精品| AV狠狠色丁香婷婷综合久久| 亚洲午夜久久久影院| 99久久夜色精品国产网站| 中文精品久久久久人妻| 伊人伊成久久人综合网777| 久久亚洲AV永久无码精品| 久久精品国产福利国产琪琪| 久久国产热这里只有精品| 久久国产免费| 中文精品99久久国产| 久久丫忘忧草产品| 思思久久99热只有频精品66| 久久综合亚洲色一区二区三区| 国产成人无码精品久久久性色 | 怡红院日本一道日本久久 | 国产精品日韩欧美久久综合| 嫩草影院久久国产精品| 国产精品免费久久久久久久久| 一级做a爰片久久毛片16| 久久高清一级毛片| 性高朝久久久久久久久久| 日本五月天婷久久网站| 亚洲AV乱码久久精品蜜桃| 久久精品九九亚洲精品| 伊人久久大香线蕉精品| 香蕉aa三级久久毛片| 午夜不卡久久精品无码免费| 精品久久久久久中文字幕| 久久久久亚洲AV无码专区桃色| 国产成人综合久久精品红| 精品久久人妻av中文字幕| 99久久精品免费国产大片| 亚洲一级Av无码毛片久久精品| 亚洲精品蜜桃久久久久久| 99久久精品费精品国产| 久久大香萑太香蕉av|