排序(一)比較排序

全文所說排序默認(rèn)升序

  • 插入排序
    • 直接插入
    • 二分插入
    • 希爾排序
  • 交換排序
    • 冒泡排序
    • 快速排序
  • 選擇排序
    • 簡單選擇排序
    • 堆排序
  • 歸并排序

一垃僚、插入排序
選擇一個(gè)元素插入到有序序列中去邢隧,使序列仍保持有序

1.直接插入排序:從序列中選擇一個(gè)元素 p ,依次和它前面的每個(gè)元素 f 比較冈在,如果 p < f倒慧,就把 f 向后移動(dòng),直到最終 p >= f,則將p的值放在當(dāng)前 f 的后面

func SInsertSort(arr []int) {
    for i:=1;i<len(arr);i++ {
        p := arr[i]
        j := i-1
        for j>=0 && p < arr[j] {
            arr[j+1] = arr[j]
            j--
        }
        arr[j+1] = p
    }
}

最壞時(shí)間復(fù)雜度:元素arr[i]要和前面的每一個(gè)元素比較纫谅,比較次數(shù)為 i*i炫贤。
所以 T_w(n) =1 + 2 +... + n-1 = O(n^2) = O(n^2)
平均時(shí)間復(fù)雜度:因?yàn)閍rr[i]的平均比較次數(shù)為 \frac{1+i}{2}
所以 T_e(n)\frac{1}{2}T_w(n) = O(n^2)
空間復(fù)雜度:S(n) = O(1)
穩(wěn)定性:穩(wěn)定(原來相等的元素相對(duì)位置不會(huì)邊)

2.二分插入排序:
利用二分查找確定應(yīng)該插入的位置付秕,可以較少比較次數(shù)兰珍,但是元素移動(dòng)次數(shù)不會(huì)減少

func BInsertSort(arr []int) {
    for i := 1; i < len(arr); i++ {
        p := arr[i]
        left := 0
        right := i - 1

        for right >= left {
            mid := (left + right) / 2
            if p < arr[mid] {
                right = mid - 1
            } else {
                left = mid + 1
            }
        }

        for j := i - 1; j >= left; j-- { //left就是p應(yīng)該放置的位置
            arr[j+1] = arr[j]
        }
        arr[left] = p
    }

    fmt.Println("二分插入", arr)
}

left就是p最終放置位置的原因:
right >= left 最后一次為true時(shí),只能是right-left == 0询吴;或者right-left=1掠河;分析這兩種情況,可以發(fā)現(xiàn)left都應(yīng)該是最終放置位置猛计。

因?yàn)橹皇菧p少了一些比較的次數(shù)唠摹,移動(dòng)次數(shù)還是不變,所以耗時(shí)是要少一些奉瘤,但復(fù)雜度肯定仍舊是O(n^2)
最壞時(shí)間復(fù)雜度T_w(n) = = O(n^2)
平均時(shí)間復(fù)雜度T_e(n) = O(n^2)
空間復(fù)雜度:S(n) = O(1)
穩(wěn)定性:穩(wěn)定(原來相等的元素相對(duì)位置不會(huì)邊)

3.希爾排序:二分和直插的移動(dòng)過程勾拉,都是每次移動(dòng)一步,比如第9個(gè)元素盗温,確定其最終該放置在arr[2]藕赞,那就要依次移動(dòng)[2,8]的所有元素,要移動(dòng)7次卖局。希爾排序的出發(fā)點(diǎn)是希望降低移動(dòng)次數(shù)斧蜕。希爾排序是設(shè)計(jì)想法是多次排序,剛開始每次移動(dòng)一大步砚偶,使其離最終位置比較接近惩激。

過程:確定一組降序的增量序列,比如9蟹演,5风钻,3,1酒请,依次用來對(duì)原序列進(jìn)行排序骡技。比如用9排序,將如下位置的元素排序
[0] [9] [18] [27]... 排序,假如為abcd羞反,排序后變成[0]=c, [9]=a, [18]=b, [27]=d
[1] [10] [19] [28]... 排序
[2] [11] [20] [29]... 排序
....

用增量5對(duì)新序列再次排序
[0] [5] [10] [15]...
[1] [6] [11] [16]...
[2] [7] [12] [17]...

微信圖片_20220822182227.jpg

5排序就是把a(bǔ)fk三個(gè)之間交換位置布朦,bgl三個(gè)之間交換位置...

func ShellSort(arr []int) {
    ds := []int{9, 5, 3, 1}

    for i := 0; i < len(ds); i++ {
        d := ds[i]

        for j := d; j < len(arr); j++ {
            p := arr[j]

            k := j - d
            for k >= 0 && p < arr[k] { // 對(duì)每個(gè)子序列進(jìn)行直接插入排序,也可以改成二分或其他
                arr[k+d] = arr[k]
                k -= d
            }

            arr[k+d] = p
        }
    }

    fmt.Println("希爾排序", arr)
}
// 希爾+二分插入
func ShellSort(arr []int) {
    ds := []int{3, 1}

    for i := 0; i < len(ds); i++ {
        d := ds[i]

        for j := d; j < len(arr); j++ {
            p := arr[j]

            left := j % d
            right := j - d
            for right >= left {
                mid := ((right-left)/d/2)*d + left
                if p < arr[mid] {
                    right = mid - d
                } else {
                    left = mid + d
                }
            }

            for k := j - d; k >= left; k -= d {
                arr[k+d] = arr[k]
            }
            arr[left] = p
        }
    }

    fmt.Println("希爾排序", arr)

}

增量序列的選取要求:

  • d[i] \le \sqrt{arr.length}
  • 遞減
  • 最后一個(gè)值必須是1

時(shí)間復(fù)雜度: 與增量序列的選取有關(guān)昼窗,分析難度較大是趴,下面是幾個(gè)序列的結(jié)論

image.png

空間復(fù)雜度:S(n) = O(1)
穩(wěn)定性:不穩(wěn)定。
比如 1澄惊,3唆途,2富雅,2' 增量序列選 2,1肛搬,得到 1 2' 2 3

二没佑、交換排序
比較元素的大小,根據(jù)結(jié)果交換位置

1.冒泡排序
下降法:從開頭向后掃描温赔,將大的放到后面去
上升法:從末尾向前掃描蛤奢,將小的放到前面去

func BubbleSort() {
    arr := []int{
        9, 5, 47, 11, 19, 6, 34, 31, 2, 10,
    }

    for sortedLen := 0; sortedLen < len(arr)-1; sortedLen++ {
        for j := len(arr) - 1; j >= 1+sortedLen; j-- {
            p := arr[j]
            if p < arr[j-1] {// 上升法,把小的換到前
                arr[j] = arr[j-1]
                arr[j-1] = p
            }
        }
    }

    fmt.Println("冒泡排序", arr)
}

缺點(diǎn):該序列已經(jīng)全部有序時(shí)陶贼,程序不能感知到啤贩。比如 1,2,5,3,第一次上升后就已經(jīng)全部有序了拜秧。
改進(jìn):

func BubbleSort() {
    arr := []int{
        9, 5, 47, 11, 19, 6, 34, 31, 2, 10,
    }

    sortedLen := 0
    hasUnSorted := true

    for hasUnSorted {
        hasUnSorted = false
        for j := len(arr) - 1; j >= 1+sortedLen; j-- {
            p := arr[j]
            if p < arr[j-1] {
                arr[j] = arr[j-1]
                arr[j-1] = p
                hasUnSorted = true
            }
        }

        sortedLen++
    }

    fmt.Println("冒泡排序", arr)

}

該版本還有缺點(diǎn)痹屹,每次可能還會(huì)和已經(jīng)有序的部分比較一下。比如4,5,6,9,8,7腹纳,第一輪從頭到尾交換后痢掠,得到4,5,6,7,9,8, 第二輪比較就應(yīng)該在7之后的位置停止

下面是一個(gè)改進(jìn)驱犹,記錄了上一輪冒泡將最小元素放在了x處嘲恍,下一次上升的上限就應(yīng)該是停止處之后(記作x+1)。

func BubbleSort() {
    arr := []int{
        9, 5, 47, 11, 19, 6, 34, 31, 2, 10,
    }

    i := 0
    for i <= len(arr)-2 {
        j := len(arr) - 2
        for j >= i {
            p := arr[j]
            if p > arr[j+1] {
                arr[j] = arr[j+1]
                arr[j+1] = p
            }
            j--
        }
        i = j + 2
    }

    fmt.Println("冒泡排序", arr)
}

最壞時(shí)間復(fù)雜度:任意兩個(gè)元素之間都逆序雄驹,最多就有\frac{n(n-1)}{2}個(gè)逆序佃牛,每次交換能減少一個(gè)逆序。 T_w(n) = O(\frac{n(n-1)}{2})≈\frac{n^2}{2}

平均時(shí)間復(fù)雜度: 逆序平均為\frac{\frac{n(n-1)}{2}+0}{2}医舆,所以T_e(n) = \frac{1}{2}T_w(n)\frac{n^2}{4}
空間復(fù)雜度:S(n) = O(1)
穩(wěn)定性:穩(wěn)定(原來相等的元素相對(duì)位置不會(huì)邊)

2.快速排序
快速排序俘侠,任意選擇一個(gè)元素,將比它大的放在其后蔬将,比它小的放在它前面爷速。然后再分別對(duì)前后兩端再次執(zhí)行此操作。

func QuickSort(arr []int, i int, j int) {
    v := arr[i]
    pv := i
    d := "end"

    for j > i {
        if d == "end" {
            if arr[j] < v {
                arr[i] = arr[j]
                pv = j
                i++
                d = "start"
            } else {
                j--
            }
        }

        if d == "start" {
            if arr[i] > v {
                arr[j] = arr[i]
                pv = i
                j--
                d = "end"
            } else {
                i++
            }

        }
    }

    arr[pv] = v

    if pv-1-i > 0 {
        QuickSort(arr, i, pv-1)
    }
    if j-pv-1 > 0 {
        QuickSort(arr, pv+1, j)
    }

}

arr := []int{
        9, 5, 47, 11, 19, 6, 34, 31, 2, 10,
    }
QuickSort(arr, 0, len(arr)-1)
fmt.Println(arr)

最好時(shí)間復(fù)雜度:O(nlogn)
最壞時(shí)間復(fù)雜度:O(n^2)
平均時(shí)間復(fù)雜度:O(nlogn)
空間復(fù)雜度:O(logn)

image.png

三霞怀、選擇排序
每次選出最大或最小的放到后面或前面

1.簡單選擇排序

func SimpleSelectSort(arr []int)  {
    for i:=0;i<len(arr)-1;i++ {
        min := i 
        for j:=i+1;j<len(arr);j++ {
            if arr[j] < arr[min] {
                min = j
            }
        }

        v := arr[i]
        arr[i] = arr[min]
        arr[min] = v 
    }
}

時(shí)間復(fù)雜度T(n) = \frac{n(n-1)}{2}
空間復(fù)雜度:S(n) = O(1)
穩(wěn)定性:穩(wěn)定(原來相等的元素相對(duì)位置不會(huì)邊)

2.堆排序
堆是一種特殊的完全二叉樹惫东,如果 父節(jié)點(diǎn)p\ge子節(jié)點(diǎn)c,稱大根堆毙石。父節(jié)點(diǎn)p\le子節(jié)點(diǎn)c廉沮,稱小根堆。

堆排序就是將序列排成堆(利用順序存儲(chǔ)的話徐矩,只需要調(diào)整序列位置即可)滞时,這樣根結(jié)點(diǎn)就是最大值(大根堆),然后將根結(jié)點(diǎn)和尾結(jié)點(diǎn)交換滤灯,這樣就最大元素就放在了正確的位置上坪稽,然后再重新堆化前面的序列曼玩,再選出當(dāng)前堆的最大,放到倒數(shù)第二個(gè)位置上...

舉例說明:
原始序列[]int{9, 5, 47, 11, 19, 6, 34, 31, 2, 10,}待使用堆排序方法排序刽漂。
將其看作一棵順序存儲(chǔ)的完全二叉樹

微信圖片_20220824170807.jpg

從最后一個(gè)非葉結(jié)點(diǎn)開始向前遍掃描演训,遇到不符合堆序的就調(diào)整順序。

  • arr[4]=19贝咙,符合堆序(大于它的所有子節(jié)點(diǎn))
  • arr[3]=11样悟,不符合堆序,arr[3] < arr[7]庭猩,交換arr[3]和arr[7]窟她,得到arr[3]=31,arr[7]=11
  • arr[2]=47,符合堆序
  • arr[1]=5蔼水,不符合堆序震糖,arr[1]<arr[3]=31,交換arr[1]和arr[3]趴腋,得到arr[1]=31吊说,arr[3]=5。此時(shí)arr[3]=5<arr[7]=11优炬,再次不符合堆序颁井,交換arr[3]和arr[7],得到arr[3]=11,arr[7]=5
  • arr[0]=9蠢护,不符合堆序雅宾,arr[0]<arr[2]...

這樣就得到了堆化好的序列 []int{47,31,34,11,19,6,9,5,2,10,}

微信圖片_20220824172148.jpg

完成了堆化之后,只需要重復(fù) “找最大值(即當(dāng)前堆根)” + “和堆尾元素交換位置” + “堆長度-1” + “重新堆化”葵硕,n-1遍即可眉抬。
第一步,交換arr[0]和arr[9]懈凹,堆范圍從arr[0]~arr[9]變成arr[0]~arr[8]
第二步蜀变,重新堆化,只需從根開始調(diào)整就行
重復(fù)...

func HeapSort() {
    arr := []int{
        9, 5, 47, 11, 19, 6, 34, 31, 2, 10,
    }

    for i := (len(arr) - 2) / 2; i >= 0; i-- {
        // (len(arr) - 2)/2是最后一個(gè)結(jié)點(diǎn)的父節(jié)點(diǎn)介评,也就是最后一個(gè)非葉結(jié)點(diǎn)
        heapfy(arr, i, len(arr)-1)
    }

    for j := 0; j < len(arr)-1; j++ {
        max := arr[0]
        arr[0] = arr[len(arr)-1-j]
        arr[len(arr)-1-j] = max
        heapfy(arr, 0, len(arr)-1-j-1)
    }

    fmt.Println("堆排序", arr)
}
func heapfy(arr []int, root int, end int) {
    leftSon := 2*root + 1
    if leftSon > end {
        return
    }

    rightSon := 2*root + 2
    max := leftSon
    v := arr[root]

    if rightSon <= end && arr[rightSon] > arr[leftSon] {
        max = rightSon
    }

    if v < arr[max] {
        arr[root] = arr[max]
        arr[max] = v
        if max*2+1 <= end {
            heapfy(arr, max, end)
        }
    }
}

完全二叉樹的順序存儲(chǔ)库北,通常從數(shù)組位置1開始存儲(chǔ),這樣比較方便計(jì)算父子結(jié)點(diǎn)位置威沫。如果一個(gè)結(jié)點(diǎn)在數(shù)組中的index是i贤惯,那其左子結(jié)點(diǎn)是2i,右子是2i+1棒掠,父結(jié)點(diǎn)是i/2(向下取整)孵构。如果要從0開始存儲(chǔ)的話,每個(gè)元素的index都會(huì)減少1烟很,i變成i-1颈墅,設(shè)i-1=k蜡镶,2i變成2i-1=2k+1,... 可以推出新的關(guān)系是 原結(jié)點(diǎn)k恤筛,左子節(jié)點(diǎn)2k+1官还, 右子結(jié)點(diǎn)2k+2 父結(jié)點(diǎn) (k-1)/2 (下整)

平均時(shí)間復(fù)雜度:O(nlogn)
最壞時(shí)間復(fù)雜度:O(nlogn)
空間復(fù)雜度:O(1)
穩(wěn)定性:不穩(wěn)定

歸并排序
歸并排序是利用分治思想,欲排序abcd毒坛,先將ab望伦,cd分別排序,然后再合并兩個(gè)有序表煎殷。

func MergeSort(arr []int) {
    if len(arr) < 2 {
        return
    }
    if len(arr) == 2 {
        v := arr[0]
        if v > arr[1] {
            arr[0] = arr[1]
            arr[1] = arr[0]
        }
        return
    }
    mid := len(arr) / 2
    MergeSort(arr[:mid])
    MergeSort(arr[mid:])
    merge(arr)
}

func merge(arr []int) {
    mid := len(arr) / 2
    i := 0
    j := mid
    k := 0

    tempArr := make([]int, len(arr))
    for i < mid && j < len(arr) {
        if arr[i] <= arr[j] {
            tempArr[k] = arr[i]
            i++
            k++
        } else {
            tempArr[k] = arr[j]
            j++
            k++
        }
    }
    for i < mid {
        tempArr[k] = arr[i]
        i++
        k++
    }
    for j < len(arr) {
        tempArr[k] = arr[j]
        j++
        k++
    }
    for i2, v := range tempArr {
        arr[i2] = v
    }
}

非遞歸版本如下:

func MergeSort(arr []int) {
    segLen := 1
    for segLen < len(arr) {
        for i := 0; i < len(arr); i += 2 * segLen {
            merge(arr, i, i+segLen)
        }
        segLen *= 2
    }
    fmt.Println("歸并排序", arr)
}

func merge(arr []int, start1 int, start2 int) {
    if start2 > len(arr)-1 {
        return
    }

    segLen := start2 - start1
    end2 := start2 + segLen - 1
    if end2 > len(arr)-1 {
        end2 = len(arr) - 1
    }

    p1 := start1
    p2 := start2
    i := 0
    tempArr := make([]int, end2-start1+1)
    for p1 < start2 && p2 <= end2 {
        if arr[p1] <= arr[p2] {
            tempArr[i] = arr[p1]
            i++
            p1++
        } else {
            tempArr[i] = arr[p2]
            i++
            p2++
        }
    }
    for p1 < start2 {
        tempArr[i] = arr[p1]
        i++
        p1++
    }
    for p2 <= end2 {
        tempArr[i] = arr[p2]
        i++
        p2++
    }
    for j := 0; j < len(tempArr); j++ {
        arr[start1+j] = tempArr[j]
    }
}

時(shí)間復(fù)雜度:O(nlogn)
空間復(fù)雜度:O(n)
穩(wěn)定性:穩(wěn)定

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末屯伞,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子豪直,更是在濱河造成了極大的恐慌劣摇,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件弓乙,死亡現(xiàn)場(chǎng)離奇詭異末融,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)暇韧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門勾习,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人锨咙,你說我怎么就攤上這事语卤∽反” “怎么了酪刀?”我有些...
    開封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長钮孵。 經(jīng)常有香客問我骂倘,道長,這世上最難降的妖魔是什么巴席? 我笑而不...
    開封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任历涝,我火速辦了婚禮,結(jié)果婚禮上漾唉,老公的妹妹穿的比我還像新娘荧库。我一直安慰自己,他們只是感情好赵刑,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開白布分衫。 她就那樣靜靜地躺著,像睡著了一般般此。 火紅的嫁衣襯著肌膚如雪蚪战。 梳的紋絲不亂的頭發(fā)上牵现,一...
    開封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音邀桑,去河邊找鬼瞎疼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛壁畸,可吹牛的內(nèi)容都是我干的贼急。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼捏萍,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼竿裂!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起照弥,我...
    開封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤腻异,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后这揣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體悔常,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年给赞,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了机打。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡片迅,死狀恐怖残邀,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情柑蛇,我是刑警寧澤芥挣,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站耻台,受9級(jí)特大地震影響空免,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜盆耽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一蹋砚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧摄杂,春花似錦坝咐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至氮昧,卻和暖如春框杜,著一層夾襖步出監(jiān)牢的瞬間浦楣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來泰國打工咪辱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留振劳,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓油狂,卻偏偏與公主長得像历恐,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子专筷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容