1. 程式人生 > >java實現排序(3)-希爾排序

java實現排序(3)-希爾排序

引言

希爾排序也是經典的排序演算法之一,其實本質上還是插入排序,不過它對插入排序做了進一步的優化。在本篇博文中會詳細介紹希爾排序,討論演算法效能,用程式碼實現希爾排序並解釋為什麼它相對於插入排序有了進一步的優化。筆者目前整理的一些blog針對面試都是超高頻出現的。大家可以點選連結:http://blog.csdn.net/u012403290

希爾排序

希爾排序是Donald Shell發明的,所以用他的名字命名。在希爾排序中,通過比較相距一定間隔的元素來工作,這一定間隔我們稱之為增量。在一趟趟的排序中,不斷的減小增量,直到比較相鄰元素(增量為1)的最後一趟排序為止。所以希爾排序也叫縮減增量排序

為了後面更好的理解,我這裡先用個例子來描述希爾排序的過程:

這裡寫圖片描述
我們來一步步分析一下上述過程:
目標資料為:8,2,3,7,5
增量序列為:2,1

A、在增量為2的時候,其實資料的分組情況是:
①組:arr[0],arr[2],arr[4],即 8,3,5為一組;
②組:arr[1],arr[3],即2,7為一組。

那麼我們就需要分別對兩組實現在上一篇博文介紹的簡單插入排序來做排序處理:
處理第①組,第一組第一個預設為是已排好序的序列(單獨一個數字,當然可以算已排好序的),拿出3來進行插入,發現3<8且8前面已經沒有資料了,所以就把3插入8前面,也就是我們上面說的“2增量1次

”;接著處理5,發現5<8,但是5>3。所以就把5插入到3和8之間,注意這個插入和冒泡的區別,在插入排序中並不是兩兩交換順序,而是要尋找待插入的數字的準確位置,插入到3和8之間的過程是,把8往後移動一位,空出5的正確插入位置!!這就是上面說的“2增量3次
處理第②組,第二組第一個預設為是已排好序的序列, 發現7比2大,直接把7插入到2的後面。也就是上面描述的“2增量2次”。
這裡有的小夥伴會問,為什麼總結的執行順序和上面討論的不一致呢?不一致的原因是,我們並不是一個小組一個小組處理的,比如說上面的原始資料中,陣列①和陣列②是穿插在一起處理的,所以變成了處理①組再處理②組的情況。

B、在增量為1的時候,其實資料就是隻有一組了:
①組 3,2,5,7,8。注意,這裡的資料是在增量為2**處理之後**的資料。
其實增量為1的時候,就是對所有的資料進行一次簡單插入排序。且只有增量為1才能保證資料是排序完畢的,所以在設定增量的時候最後一個增量肯定是1,不然你的希爾排序就是不正確的。這裡的排序過程就是簡單的插入排序,會進行N-1輪(這裡為4輪)的簡單插入排序,可以檢視前面介紹插入排序的博文,這裡不再贅述。

通過上面的簡單瞭解,我們在進一步介紹一下希爾排序的核心模組:增量序列。
從上面的例子可以看出,只要保證最後一個增量為1,那麼任何增量序列都是可行的。對於上面的排序既可以[2、1]作為增量,你還可以用 [3、1];[4、1];[4、3、1]等等。所以,這裡就會暴露出一個問題,如何選擇增量將會直接影響到你排序的效能問題,這個在後面我會用程式碼測試執行時間來闡述。
希爾排序的時間複雜度依賴於增量序列的選擇,所以不同的增量序列,他們所導致的時間複雜度是不同的。

希爾增量序列

希爾增量序列是指這樣的一種增量序列:h = N/2,h/2…..1。的過程,就如上面的有5個元素進行排序,那麼它的希爾增量序列就是5/2 = 2, 2/2 = 1,也就是[2、1]增量序列。同時在希爾增量序列下,希爾排序的時間複雜度為O(N^2)

Hibbard增量序列

Hibbard增量序列是一個更有經驗的序列,它描述這麼一個序列:1,3,7…….2^k-1。關鍵的因素是相鄰的增量是不存在公因子的。這個時候的希爾排序時間複雜度就為O(N^(3/2))

在後面,我們嘗試比較①希爾增量下的希爾排序和②Hibbard增量下的希爾排序的執行效率。
在程式碼實現之前,我們總結以下實現的規律:
①存在一個增量序列,且這個增量序列的最後一位為1。同時在每一個增量序列下需要處理一次資料,那麼狠明顯,我們需要依賴增量序列進行一個for迴圈操作。
②對於按增量序列分組後的資料,對每個組的資料其實是一次簡單的插入排序。所以核心底層應該還是簡單插入排序。
③我們前面也說道,雖然說每一個增量分組需要單獨的進行簡單的插入排序,但是作為一個整體的目標資料,進行多陣列拆分顯然是不可能的,所以我們需要交叉處理各個陣列。

程式碼實現希爾增量下的希爾排序

package com.brickworkers;

import java.awt.color.ICC_ColorSpace;

/**
 * 
 * @author Brickworker
 * Date:2017年4月27日下午2:54:00 
 * 關於類ShellSort.java的描述:希爾排序
 * Copyright (c) 2017, brcikworker All Rights Reserved.
 */
public class ShellSort {

    //希爾增量下的希爾排序
    //為了使程式碼更加的通用,我們用泛型來書寫,同時和前幾篇一樣實現comparable介面
    public static<T extends Comparable<? super T>> void shellsort(T[] target){//用T表示泛型
        int j;//標記當前資料應該要插入的位置

        //第一個for迴圈,迴圈增量
        //希爾增量增量大於0,且第一個增量為目標資料總長度的一半取整,後面的增量都為前面增量的一半取整
        for(int increment = target.length / 2; increment > 0; increment /=2){

            //簡單的插入排序
            //遍歷目標資料,從第一個增量開始,第0個增量預設排序好的
            for(int i = increment; i < target.length; i++){
                //把帶插入的資料用temp暫存起來
                T temp = target[i];
                //尋找準確的插入位置
                //其實核心是在增量的分組下尋找這個值在某一個分組下應該屬於它的位置
                for(j = i; j >= increment&&temp.compareTo(target[j - increment]) < 0; j-=increment){//注意,因為是處理分組的,所以不能和簡單插入排序一樣j--
                    //如果當前位置的資料比前一個數據小,那麼就需要把前面資料往後移動一位
                    target[j] = target[j - increment];//這裡並不是冒泡的交換位置!!
                }
                target[j] = temp;//把資料插入到準確的地方,這個才是插入排序
                //列印每次排序後的結果
                String result = "";
                for (T t : target) {
                    result += t+" ";
                }
                System.out.println(increment+"增量的排序結果:" + result);

            }
        }
    }

public static void main(String[] args) {

        Integer[] target = {8,2,3,7,5};
        shellsort(target);
    }
}

    //輸出結果:
//  2增量排序結果:3 2 8 7 5 
//  2增量排序結果:3 2 8 7 5 
//  2增量排序結果:3 2 5 7 8 
//  1增量排序結果:2 3 5 7 8 
//  1增量排序結果:2 3 5 7 8 
//  1增量排序結果:2 3 5 7 8 
//  1增量排序結果:2 3 5 7 8 

大家仔細檢視輸出結果,和我們在文章開頭所描述的希爾排序示例是一模一樣的。接下來我們嘗試用程式碼實現Hibbard來實現希爾排序,有了上面的基礎,這裡就會簡單很多,只需要把increment做一下修改即可。

package com.brickworkers;

import java.util.Random;

/**
 * 
 * @author Brickworker
 * Date:2017年4月27日下午2:54:00 
 * 關於類ShellSort.java的描述:希爾排序
 * Copyright (c) 2017, brcikworker All Rights Reserved.
 */
public class ShellSort {

    //直接定義一種增量列舉型別
    private static enum Type{
        SHELL, HIBBARD;
    }

    public static<T extends Comparable<? super T>> void shellsort(T[] target, Type type){//修改原來的方法,新增一個增量型別引數
        //但是要保證兩者的增量數是一致的,所以用size標記增量個數
        int size = 0;
        for(int increment = target.length / 2; increment > 0; increment /=2){
            size++;
        }
        switch (type) {
        case SHELL:
            long startTimeShell = System.currentTimeMillis();
            for(int increment = target.length / 2; increment > 0; increment /=2){
                dataHanding(target, increment);
            }
            System.out.println("希爾增量排序耗時:"+ (System.currentTimeMillis() - startTimeShell));
            break;
        case HIBBARD:
            long startTimeHibbard = System.currentTimeMillis();
            for(int increment = (int) (Math.pow(2, size)-1); size > 0; increment = (int) (Math.pow(2, --size)-1)){
                dataHanding(target, increment);
            }
            System.out.println("Hibbard增量排序耗時:"+ (System.currentTimeMillis() - startTimeHibbard));
            break;

        default:
            break;
        }


    }

    private static <T extends Comparable<? super T>> void dataHanding(T[] target, int increment) {
        int j ;//標記當前資料應該要插入的位置
        //簡單的插入排序
        //遍歷目標資料,從第一個增量開始,第0個增量預設排序好的
        for(int i = increment; i < target.length; i++){
            //把帶插入的資料用temp暫存起來
            T temp = target[i];
            //尋找準確的插入位置
            //其實核心是在增量的分組下尋找這個值在某一個分組下應該屬於它的位置
            for(j = i; j >= increment&&temp.compareTo(target[j - increment]) < 0; j-=increment){//注意,因為是處理分組的,所以不能和簡單插入排序一樣j--
                //如果當前位置的資料比前一個數據小,那麼就需要把前面資料往後移動一位
                target[j] = target[j - increment];//這裡並不是冒泡的交換位置!!
            }
            target[j] = temp;//把資料插入到準確的地方,這個才是插入排序
/*          //列印每次排序後的結果
            String result = "";
            for (T t : target) {
                result += t+" ";
            }
            System.out.println(increment+"增量的排序結果:" + result);*/
        }
    }


    public static void main(String[] args) {

        //製造一個2000個數據的無序序列
        Integer[] target = new Integer[20000];
        for (int i = 0; i < 20000; i++) {
            target[i] = new Random().nextInt(2000);
        }

        shellsort(target, Type.SHELL);
        shellsort(target, Type.HIBBARD);
    }
}


//輸出結果:
//希爾增量排序耗時:13
//Hibbard增量排序耗時:8

用一個靜態內部列舉類表示情況分類,同時用size保證兩種增量的數量是一致的。因為中間的一段程式碼是複用的,所以我用eclipse中的Extract Method方法把中間重複部分抽離出來形成了一個新的方法。對於一個20000個數據的排序,發現用Hibbard增量的希爾排序會比希爾增量的希爾排序快的多,大家可以自己嘗試。

尾記

希爾排序是插入排序的一種優化,核心仍舊是插入排序。那麼為什麼會使得效能有所提升呢?在簡單的插入排序中,我們討論過如果目標資料本身就是比較有序的那麼它的排序效率就會高很多,因為它減少了複製的次數。那麼在希爾排序中,存在優越的增量情況下,可以減少資料比較和複製的次數。所以希爾排序的高效性和增量序列有極大的關係,一個好的增量序列才能提升關鍵效率。如果大家有興趣,可以把簡單插入排序也列入比較目標,3中型別進行比較很快就能發現各自之間的效率問題。

希望對大家有所幫助

相關推薦

java實現排序(3)-排序

引言 希爾排序也是經典的排序演算法之一,其實本質上還是插入排序,不過它對插入排序做了進一步的優化。在本篇博文中會詳細介紹希爾排序,討論演算法效能,用程式碼實現希爾排序並解釋為什麼它相對於插入排序有了進一步的優化。筆者目前整理的一些blog針對面試都是超高頻出現

3. 排序通常有多種演算法,如氣泡排序、插入排序、選擇排序排序、歸併排序、快速排序,請選擇任意2種用java實現 [分值:20] 您的回答:(空) (簡答題需要人工評分)

3. 排序通常有多種演算法,如氣泡排序、插入排序、選擇排序、希爾排序、歸併排序、快速排序,請選擇任意2種用java實現  [分值:20] 您的回答:(空)  (簡答題需要人工評分) package com.interview; /** * 各種排序演算法 */

演算法(3) 排序 java

簡介:希爾排序的實質其實是分組插入排序,再通俗的講就是縮小增量排序,是一種比O(N^2)要好的排序演算法,當然,是比不上O(NlgN)的演算法. 原理:將整個待排元素序列分割成若干個子序列(由相隔某個增量值gap的元素組成),再分別對每個子序列進行快速插入排序,然後減少gap再進行快速插入排序,

java實現直接插入排序排序

直接插入排序和希爾排序,把這兩個放一起是便於記憶,這兩個排序是差不多的,希爾排序也只是對插入排序進行一點修改: 首先是看一下我們的插入排序: package sort; public class InsertSort { public static void main

插入排序排序Java實現

1、插入排序 插入排序就是每一步都將一個待排資料按其大小插入到已經排序的資料中的適當位置,直到全部插入完畢。 2、插入排序Java程式碼實現 /** * @Comment 插入排序 * @Author Ron *

Java實現快速排序、歸併排序、堆排序排序

快速排序 演算法思想 1.將陣列的第一個元素取為target,定義兩個指標i 和 j; 2.指標i ,從左向右找到第一個比target大的元素,指標j從右向左找到第一個比target小的元素,

插入排序排序 Java實現以及實際效率對比

插入排序 以及其改進型 希爾排序的Java實現以及實際效能對比簡單對插入排序和希爾排序做一個介紹,以及給出Java實現,之後簡單分析下兩者的時間複雜度,以及實際表現插入排序:一般從陣列第二個元素(下標為

java實現---插入排序----直接插入排序排序

插入排序 直接插入排序 希爾排序 直接插入排序 直接插入排序,把待排序的元素插入到前面排好序的一組元素的合適位置上去 在前面已經排好序的元素中,從後往前找 publ

歸併排序排序、快速排序的基本思想過程及java實現

歸併排序 歸併排序是根據將兩個有序數組合併成一個有序陣列的思想發展而來,如果兩個陣列有序,那麼只需要依次比較兩個陣列中的每個數,小的數放進空陣列中,並原陣列遊標向前移動一位進行下一次比較,直到當一個數組的遊標到達陣列尾部(也就是這個陣列被比較完了)時,直接將另一個數組剩

【資料結構】Java實現各類經典排序演算法——插入排序排序

一、插入排序    顧名思義,插入排序從左往右掃描陣列,每趟排序把一個元素“插入”到已排序部分陣列的合適位置中。既然是“插入”,則不必兩兩交換元素來進行排序,從邏輯上把當前元素放到合適位置,並把該位置右側部分元素往右移動一格就可以了。這樣做和氣泡排序的交換相鄰元素比,好處在於

【算法拾遺(java描寫敘述)】--- 插入排序(直接插入排序排序

ecan itblog insert med image java程序 can rip title 插入排序基本思想 每次將一個待排序的記錄按其keyword大小插入到前面已經拍好序的子文件的適當位置,直到全部記錄插入完畢為止。 直接插入

插入排序排序--java

插入排序 插入排序的程式碼實現雖然沒有氣泡排序和選擇排序那麼簡單粗暴,但它的原理應該是最容易理解的了,因為只要打過撲克牌的人都應該能夠秒懂。插入排序是一種最簡單直觀的排序演算法,它的工作原理是通過構建有序序列,對於未排序資料,在已排序序列中從後向前掃描,找到相應位置並插入。 插入排序

順序表儲存實現氣泡排序,選擇排序,插入排序排序,基數排序

#include<iostream> using namespace std; const int MAXSIZE = 100; typedef int ElemType; struct Data { ElemType key; // int shu

Java 排序之插入排序排序

今天學習了一下 Java 陣列的相關操作,包含排序和查詢,現在先將排序記錄鞏固一下。 常用並且比較重要的幾種排序:插入排序、希爾排序、快速排序、歸併排序、氣泡排序、選擇排序。 一、插入排序 1. 插入排序演算法思想 1) 普遍思想 插入排序的演算法是一種簡單直

排序演算法 | 排序演算法原理及實現和優化

希爾排序也是一種插入排序演算法,也叫作縮小增量排序,是直接插入排序的一種更高效的改進演算法。 希爾排序因其設計者希爾(Donald Shell)的名字而得名,該演算法在 1959 年被公佈。一些老版本的教科書和參考手冊把該演算法命名為 Shell-Metzner

C語言中常用排序演算法(氣泡排序、選擇排序、插入排序排序、快速排序、堆排序實現比較

以下程式在win10 X64位作業系統,使用VS2017執行驗證可行 排序是非常重要且很常用的一種操作,有氣泡排序、選擇排序、插入排序、希爾排序、快速排序、堆排序等多種方法。 例項1 冒泡法排序 1.前言: 陣列中有N個整數,用冒泡法將它們從小到大(或從大到小)排序。冒泡法

C語言實現排序演算法---排序

今天又重新研究了一遍諸多排序演算法,現在簡單分享一下里面的希爾排序(Shell Sort)的心得。 希爾排序(Shell Sort)是插入排序的一種。也稱縮小增量排序,是直接插入排序演算法的一種

Java面試寶典——排序+堆排序

package demos.order; /** * @author wyl * @time 2018年7月12日下午1:43:43 * 希爾排序:即縮小增量排序 * 基本原理: * 先將

PHP實現排序演算法----排序(Shell Sort)

基本思想: 希爾排序是指記錄按下標的一定增量分組,對每一組使用 直接插入排序 ,隨著增量逐漸減少,每組包含的關鍵字越來越多,當增量減少至 1 時,整個序列恰好被分成一組,演算法便終止。 操作步驟: 先取一個小於 n(序列記錄個數) 的整數 d1 作為第一個

各種排序演算法的場景以及c++實現(插入排序排序,氣泡排序,快速排序,選擇排序,歸併排序

對現有工作並不是很滿意,所以決定找下一個坑。由工作中遇到排序場景並不多,大都是用氣泡排序,太low,面試又經常問到一些排序演算法方面的東西。剛好讓小學妹郵的資料結構也到了。就把各種排序演算法重新總結一下,以作留存。 排序分為內部排序和外部排序,內部排序是在記憶體中排序。外