數據結構和算法(Golang實現)(20)排序算法-選擇排序

選擇排序

選擇排序,通常咱們指的是簡單選擇排序,也能夠叫直接選擇排序,它不像冒泡排序同樣相鄰地交換元素,而是經過選擇最小的元素,每輪迭代只需交換一次。雖然交換次數比冒泡少不少,但效率和冒泡排序同樣的糟糕。算法

選擇排序屬於選擇類排序算法。segmentfault

我打撲克牌的時候,會習慣性地從左到右掃描,而後將最小的牌放在最左邊,而後從第二張牌開始繼續從左到右掃描第二小的牌,放在最小的牌右邊,以此反覆。選擇排序和我玩撲克時的排序特別類似。數組

1、算法介紹

如今有一堆亂序的數,好比:5 9 1 6 8 14 6 49 25 4 6 3數據結構

第一輪迭代,從第一個數開始,左邊到右邊進行掃描,找到最小的數 1,與數列裏的第一個數交換位置。併發

第二輪迭代,從第二個數開始,左邊到右邊進行掃描,找到第二小的數 3,與數列裏的第二個數交換位置。數據結構和算法

第三輪迭代,從第三個數開始,左邊到右邊進行掃描,找到第三小的數 4,與數列裏的第三個數交換位置。函數

第N輪迭代:....優化

通過交換,最後的結果爲:1 3 4 5 6 6 6 8 9 14 25 49,咱們能夠看到已經排好序了。code

每次掃描數列找出最小的數,而後與第一個數交換,而後排除第一個數,從第二個數開始重複這個操做,這種排序叫作簡單選擇排序。協程

舉個簡單例子,選擇排序一個 4 個元素的數列:4 2 9 1

[]表示排好序

起始: 4 2 9 1  未排序數列從左掃描最小的數是 1,與第一個元素 4 交換,交換 1,4
一輪: [1] 2 9 4 未排序數列從左掃描最小的數是 2,不須要交換
二輪: [1 2] 9 4 未排序數列從左掃描最小的數是 4,與第三個元素 9 交換,交換 4,9
三輪: [1 2 4] 9 未排序數列只有 1 個數,結束
結果: [1 2 4 9]

比較的次數和冒泡排序同樣多,由於掃描過程也是比較的過程,只不過交換的次數減小爲每輪 1 次。最佳和最壞時間複雜度仍然是:O(n^2)

選擇排序是一個不穩定的排序算法,好比數組:[5 6 5 1],第一輪迭代時最小的數是1,那麼與第一個元素5交換位置,這樣數字1就和數字5交換了位置,致使兩個相同的數字5排序後位置變了。

2、算法實現

package main

import "fmt"

func SelectSort(list []int) {
    n := len(list)
    // 進行 N-1 輪迭代
    for i := 0; i < n-1; i++ {
        // 每次從第 i 位開始,找到最小的元素
        min := list[i] // 最小數
        minIndex := i  // 最小數的下標
        for j := i + 1; j < n; j++ {
            if list[j] < min {
                // 若是找到的數比上次的還小,那麼最小的數變爲它
                min = list[j]
                minIndex = j
            }
        }

        // 這一輪找到的最小數的下標不等於最開始的下標,交換元素
        if i != minIndex {
            list[i], list[minIndex] = list[minIndex], list[i]
        }
    }
}

func main() {
    list := []int{5, 9, 1, 6, 8, 14, 6, 49, 25, 4, 6, 3}
    SelectSort(list)
    fmt.Println(list)
}

每進行一輪迭代,咱們都會維持這一輪最小數:min和最小數的下標:minIndex,而後開始掃描,若是掃描的數比該數小,那麼替換掉最小數和最小數下標,掃描完後判斷是否應交換,而後交換:list[i], list[minIndex] = list[minIndex], list[i]

3、算法改進

上面的算法須要從某個數開始,一直掃描到尾部,咱們能夠優化算法,使得複雜度減小一半。

咱們每一輪,除了找最小數以外,還找最大數,而後分別和前面和後面的元素交換,這樣循環次數減小一半,如:

package main

import "fmt"

func SelectGoodSort(list []int) {
    n := len(list)

    // 只需循環一半
    for i := 0; i < n/2; i++ {
        minIndex := i // 最小值下標
        maxIndex := i // 最大值下標

        // 在這一輪迭代中要找到最大值和最小值的下標
        for j := i + 1; j < n-i; j++ {
            // 找到最大值下標
            if list[j] > list[maxIndex] {
                maxIndex = j // 這一輪這個是大的,直接 continue
                continue
            }
            // 找到最小值下標
            if list[j] < list[minIndex] {
                minIndex = j
            }
        }

        if maxIndex == i && minIndex != n-i-1 {
            // 若是最大值是開頭的元素,而最小值不是最尾的元素
            // 先將最大值和最尾的元素交換
            list[n-i-1], list[maxIndex] = list[maxIndex], list[n-i-1]
            // 而後最小的元素放在最開頭
            list[i], list[minIndex] = list[minIndex], list[i]
        } else if maxIndex == i && minIndex == n-i-1 {
            // 若是最大值在開頭,最小值在結尾,直接交換
            list[minIndex], list[maxIndex] = list[maxIndex], list[minIndex]
        } else {
            // 不然先將最小值放在開頭,再將最大值放在結尾
            list[i], list[minIndex] = list[minIndex], list[i]
            list[n-i-1], list[maxIndex] = list[maxIndex], list[n-i-1]
        }
    }
}

func main() {
    list := []int{5}
    SelectGoodSort(list)
    fmt.Println(list)

    list1 := []int{5, 9}
    SelectGoodSort(list1)
    fmt.Println(list1)

    list2 := []int{5, 9, 1}
    SelectGoodSort(list2)
    fmt.Println(list2)

    list3 := []int{5, 9, 1, 6, 8, 14, 6, 49, 25, 4, 6, 3}
    SelectGoodSort(list3)
    fmt.Println(list3)

    list4 := []int{5, 9, 1, 6, 8, 14, 6, 49, 25, 4, 6}
    SelectGoodSort(list4)
    fmt.Println(list4)
}

輸出:

[5]
[5 9]
[1 5 9]
[1 3 4 5 6 6 6 8 9 14 25 49]
[1 4 5 6 6 6 8 9 14 25 49]

優化後的選擇排序仍是很慢,它很好理解,可是仍是不建議在工程上使用。

系列文章入口

我是陳星星,歡迎閱讀我親自寫的 數據結構和算法(Golang實現),文章首發於 閱讀更友好的GitBook

相關文章
相關標籤/搜索