排序之希爾排序(shell sort)


前言

  本篇博客是在伍迷兄的博客基礎上進行的,其博客地址點擊就可以進去,里面好博客很多,我的排序算法都來自於此;一些數據結構方面的概念我就不多闡述了,伍迷兄的博客中都有詳細講解,而我寫這些博客只是記錄自己學習過程,加入了一些自己的理解,同時也希望給別人提供幫助。

前提故事

   騷年在上次與博主進行了直接插入排序的討論后,找到了博主,說:“博主,對於直接插入排序,我有重大的發現”,博主想了想,就問:“什么發現?”,騷年:“我發現了如下兩點”

    1)當序列的個數比較少時,直接插入排序效率高;這個好理解,個數比較少,那么插入的次數也就少了,博主就說:“恩,這個發現不難,卻也需要細心”。

    2)如果序列本身就是基本有序,那么直接插入排序效率高;博主:“嗯?”,騷年解釋道:“你看直接插入排序的核心代碼:”

       

     for(int i=1; i<len; i++){                            
            for(int j=i-1; j>=0&&arr[j]>arr[j+1]; j--){        
                swap(arr,j,j+1);
            }
        }

    騷年接着道:“如果序列有序,那么j>=0&&arr[j]>arr[j+1]條件就是不滿足的,插入操作就不會執行,效率自然就高了。”

    博主:“然后了?”。

    騷年:“那么我們是不是可以在這兩點上做點事,來提高直接插入排序在普通序列上的效率了?”。

    上述兩個條件過於苛刻,現實中記錄少或者基本有序都屬於特殊情,有條件當然是好,條件不存在,我們創造條件,也是可以去做的;騷年與博主進行了研究與討論,我們可以對序列進行分組,分割成若干個子序列,然后對每個子序列分別進行直接插入排序,當整個序列都基本有序時,注意只是基本有序時,再對全體記錄進行一次直接插入排序

    此時一定有人開始疑惑了。這不對呀,比如我們現在有序列是{5,3,7,9,1,6,4,8,2},現在將它分成三組,{5,3,7}, {9,1,6},{4,8,2},哪怕將它們各自排序排好了,變成{3,5,7},{1,6,9},{2,4,8},再合並它們成 {3,5,7,1,6,9,2,4,8},此時,這個序列還是雜亂無序,談不上基本有序,要排序還是重來一遍直接插入有序,這樣做有用嗎?需要強調一下, 所謂的基本有序,就是小的關鍵字基本在前面,大的基本在后面,不大不小的基本在中間,像{2,1,3,6,4,7,5,8,9}這樣可以稱為基本有序了。 但像{3,5,7,1,6,9,2,4,8}這樣的7在第三位,2在倒數第三位就談不上基本有序。
        那么問題就來了,我們分割待排序記錄的目的是減少待排序記錄的個數,並使整個序列向基本有序發展。而如上面這樣分完組后,就各自排序的方法達不到我們的要求。因此,我們需要采取跳躍分割的策略:將相距某個“增量”的記錄組成一個子序列,這樣才能保證在子序列內分別進行直接插入排序后得到的結果是基本有序而不是局部有序

基本思想

  將整個序列按照相距某個“增量”進行拆分,然后逐個對子序列進行直接插入排序,使得得到的結果基本有序,最后對基本有序的序列進行一次直接插入排序,使得整個序列有序

代碼實現

  java實現

       /**
     * 希爾排序 
     * @param arr 目標序列
     */
    public static void shellSort(int[] arr){
        int len = arr.length;
        for(int gap=len/2; gap>=1; gap=gap/2){                    //拆分整個序列,元素間距為gap(也就是增量)
            //對子序列進行直接插入排序
            for(int i=gap+1; i<len; i++){
                for(int j=i-gap; j>=0&&arr[j]>arr[j+gap]; j=j-gap){        
                    swap(arr,j,j+gap);
                }
            }
        }
    }    
View Code

執行過程模擬

  1)程序開始執行,初始序列為{5,3,7,9,1,6,4,8,2},如下圖:

  2)初始gap=len/2=4

    2.1)i=gap=4,初始j=0;比較arr[j]與arr[j+gap],即arr[0]與arr[4],如下圖:

      j=j-gap=-4,跳出,i++,i=5

    2.2)i=5,j=i-gap=1,arr[1]=3 < arr[5]=6,不交換數據,如下圖:

    

      j=j-gap=-3,跳出,i++,i=6

    2.3)同理,當i=6,7時,序列如下圖:

 

    2.4)當i=8時,序列如下:

    那么gap=4時,最終序列為

  3)gap=gap/2=2

    3.1)i=gap=2,j=i-gap=0,arr[0]=1 < arr[2]=4不交換,j=j-gap=-2,i++,此時序列為:

    3.2)i=3,j=i-gap=1,arr[1]=3 < arr[3]=8,不交換,j=j-gap=-1,i++,此時序列為:

    3.3)同理,i=4時:

    3.4)i=5時:

    3.5)i=6時:

    3.6)i=7時:

    3.7)i=8時:

  4)gap=gap/2=1,此時

    for(int gap=len/2; gap>=1; gap=gap/2){                    //拆分整個序列,元素間距為gap(也就是增量)
            //對子序列進行直接插入排序
            for(int i=gap; i<len; i++){
                for(int j=i-gap; j>=0&&arr[j]>arr[j+gap]; j=j-gap){        
                    swap(arr,j,j+gap);
                }
            }

    就是

            //對子序列進行直接插入排序
            for(int i=1; i<len; i++){
                for(int j=i-1; j>=0&&arr[j]>arr[j+1]; j=j-1){        
                    swap(arr,j,j+1);
                }
            }

    相信大家都發現了,上面代碼就是我們的直接插入排序,那么具體的模擬過程我也就不再贅述了,不懂的可以去看排序之直接插入排序

  至此,整個序列就有序了。

難解之處

  通過這段代碼的剖析,相信大家有些明白,希爾排序的關鍵並不是隨便的分組后各自排序,而是將相隔某個“增量”的記錄組成一個子序列,實現跳躍式的移動,使得排序的效率提高。這里“增量”的選取就非常關鍵了,本例中是以gap=gap/2的方式選取增量的,可究竟應該選取什么樣的 增量才是最好,目前還是一個數學難題,迄今為止還沒有人找到一種最好的增量序列。不過大量的研究表明,當增量序列為dlta[k]=2t-k+1-1(0≤k≤t≤⌊log2(n+1)⌋)時,可以獲得不錯的效率。需要注意的是,增量序列的最后一個增量值必須等於1才行。 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM