程序猿修仙之路--算法之希爾排序

自馮諾依曼開啓大計算機時代以來,通過近一個世紀的蓬勃發展,已然成爲一我的才衆多的羣體:IT江湖
依附市場規律,江湖上悄然興起數十宗門,其中以AI,大數據近期最爲熱門。
每一個宗門人才輩出,搶奪人才大戰早已在阿里,騰訊,百度等數百個國度白熱化。
IT江湖人士憑藉JAVA,Python等武器,在精通各路內功心法的基礎上在各個國度揚名立萬,修仙成佛者衆多,爲後人樹下追寵之榜樣。算法

內功心法衆多,其中以算法最爲精妙,是修仙德道必經之路

雖然江湖上算法內功繁多,可是好的算法小編認爲必須符合如下幾個條件,方能真正提升習練者實力。c#

  • 時間複雜度(運行時間)
在算法時間複雜度維度,咱們主要對比較和交換的次數作對比,其餘不交換元素的算法,主要會以訪問數組的次數的維度作對比。

其實有不少修煉者對於算法的時間複雜度有點模糊,分不清什麼所謂的 O(n),O(nlogn),O(logn)...等,也許下圖對一些人有一些更直觀的認識。數組

image

  • 空間複雜度(額外的內存使用)
排序算法的額外內存開銷和運行時間同等重要。 就算一個算法時間複雜度比較優秀,空間複雜度很是差,使用的額外內存很是大,菜菜認爲它也算不上一個優秀的算法。
  • 結果的正確性
這個指標是菜菜本身加上的,我始終認爲一個優秀的算法最終獲得的結果必須是正確的。就算一個算法擁有很是優秀的時間和空間複雜度,可是結果不正確,致使修煉者經脈逆轉,走火入魔,又有什麼意義呢?

原理

在上一篇咱們修煉了插入排序,希爾排序(又名Shell's Sort)本質上屬於插入排序,是插入排序的一種更高效升級版本,也稱爲縮小增量排序。同時希爾排序在時間複雜度上也是突破O(n²)的第一批算法之一。你說厲不厲害?~~網絡

基本思想

經過直接插入排序的修煉,咱們知道直接插入排序是一種性能比較低的初級算法,對修煉者提高不是不大, 可是有一點優點那就是對於小型數組或者部分有序的數組很是高效,希爾排序就是基於這一點優點對直接插入排序進行了改良。換句話說直接插入排序低效的緣由在於無序,無序的程度越高越低效。例如:最小的元素初始位置在數組的另外一端,此元素要想到達正確位置,是須要一個一個位置前移,最終須要N-1次移動。如何改變這種狀態正是希爾排序的突破口。
希爾排序的思想是把數組下標按照必定的增量h分組,而後對每組進行直接插入排序。在進行排序時,若是h很大,咱們就能將元素移動到很遠的地方,爲實現更小的h有序創造方便。而後增量h逐漸減少(每一個分組的元素量增多),直到h爲1整個數組劃分爲一組,排序結束。
也許一張更直觀的圖比上千句話效果都好

image

複雜度

  • 時間複雜度

最壞時間複雜度依然爲O(n²),一些通過優化的增量序列如Hibbard通過複雜證實可以使得最壞時間複雜度爲O(n^3/2),最好狀況下爲O(n)屬於線性複雜度。app

  • 空間複雜度

優於希爾排序本質上屬於插入排序升級版,因此空間上和直接插入排序一致爲O(1),在常數級別。dom

性能和特色

  • 希爾排序之因此高效是由於它權衡了子數組的規模和有序性。排序之初各個子數組都很短,這種狀況很適合插入排序。
  • 對於增量h的選擇對希爾排序很是重要,直接影響其性能。其實除了h的選擇以外,h之間的數學性質也影響希爾排序的性能,好比它們的公因子等。不少論文研究了各類不一樣的遞增序列,但都沒法證實某個序列是最好的。對於某些基礎遞增的序列其實在性能上和某些複雜的序列接近,因此不少狀況下咱們沒有必要花大力氣在複雜序列上的研究上。
適用場景

與插入排序不一樣,希爾排序能夠適用於大型數組,它對任意排序的數組表現良好,雖然不是最好。實驗證實,希爾排序比咱們上兩章學習的選擇排序和插入排序要快的多,而且數組越大,優點越大。
目前最重要的結論是:希爾排序的運行時間達不到平方級別。
對於中等大小的數組希爾排序的時間是在可接受範圍以內的,由於它的代碼量很小,並且須要的額外空間很小,幾乎能夠忽略。對於其餘更高效的其餘算法,可能比希爾排序更高效,可是代碼也更復雜,性能上比希爾排序也高不了幾倍,因此在不少狀況下希爾排序成爲首選的算法。性能

其餘

直接插入排序是穩定的,希爾排序呢?

因爲屢次插入排序,咱們知道一次插入排序是穩定的,不會改變相同元素的相對順序,但在不一樣的插入排序過程當中,相同的元素可能在各自的插入排序中移動,最後其穩定性就會被打亂,因此希爾排序排序是不穩定的。學習

試煉一發吧

c# 武器版
static void Main(string[] args)
        {
            List<int> data = new List<int>() ;
            for (int i = 0; i < 11; i++)
            {
                
                data.Add(new Random(Guid.NewGuid().GetHashCode()).Next(1, 100));
            }
            //打印原始數組值
            Console.WriteLine($"原始數據: {string.Join(",", data)}");
            int n = data.Count;
            int h = 1;
            //計算初始化增量,網絡提供,聽說比較好的遞增因子
            while (h < n / 3)
            {
                h = 3 * h + 1;
            }
            Console.WriteLine($"初始化增量:{h}");
            while (h >= 1)
            {
                for (int i = h; i < n; i++)
                {
                    for (int j = i; j >=h&&data[j]<data[j-h]; j-=h)
                    {
                        //異或法 交換兩個變量,不用臨時變量
                        data[j] = data[j] ^ data[j - 1];
                        data[j - 1] = data[j] ^ data[j - 1];
                        data[j] = data[j] ^ data[j - 1];
                    }
                }
                h = h / 3;
            }


            //打印排序後的數組
            Console.WriteLine($"排序數據: {string.Join(",", data)}");
            Console.Read();
        }

運行結果:大數據

原始數據: 47,50,32,42,44,79,10,16,51,74,52

初始化增量:4優化

排序數據: 10,16,32,42,44,47,50,51,52,74,79

Golang 武器版
package main

import (
    "fmt"
    "math/rand"
)

func main() {
    var data []int
    for i := 0; i < 11; i++ {
        data = append(data, rand.Intn(100))
    }
    fmt.Println(data)
    var n = len(data)
    var h = 1
    for h < n/3 {
        h = 3*h + 1
    }
    fmt.Println(h)
    for h >= 1 {
        for i := h; i < n; i++ {
            for j := i; j >= h && data[j] < data[j-h]; j -= h {
                data[j], data[j-h] = data[j-h], data[j]
            }
        }
        h = h / 3
    }
    fmt.Println(data)
}

運行結果:

[81 87 47 59 81 18 25 40 56 0 94]

4

[0 18 25 40 47 56 59 81 81 87 94]


添加關注,查看更精美版本,收穫更多精彩

image

相關文章
相關標籤/搜索