1. 程式人生 > 其它 >資料結構和演算法:基礎排序演算法(Python)

資料結構和演算法:基礎排序演算法(Python)

基礎排序演算法雖然在實際開發中很少用到,但是在面試的時候卻有很大概率被問到,可能不會要你現場寫一個排序演算法,但通常會問你某種演算法的原理或者排序方法,所以在這裡重新整理一下一些基礎的排序演算法。

1. 氣泡排序

原理

每次遍歷找到其中的最大值:從左到右遍歷全部n個元素,每次比較相鄰兩個元素的大小,將較大的一個放到右邊(“冒泡”),直到第n-1個元素與第n個元素的比較後,整個序列的最大元素就“冒泡”到了最後,接著以同樣的方法重新遍歷第1個元素到n-1個元素,找出這n-1個元素中最大的元素,並“冒泡”到最後的位置。這樣多次遍歷後就形成了一個從左到右升序的序列。

示例程式碼

"""氣泡排序"""
import random


def bubble_sort(array):
    for i in range(len(array) - 1):
        for j in range(len(array) - 1 - i):
            if array[j] > array[j+1]:
                array[j], array[j+1] = array[j+1], array[j]


if __name__ == '__main__':
    # 產生一個有10個元素的亂序的列表
    random_array = list(random.randint(1, 100) for _ in range(10))
    print(f'Before sorted: {random_array}')
    bubble_sort(random_array)
    print(f'After sorted: {random_array}')
Before sorted: [65, 36, 12, 18, 28, 65, 82, 93, 89, 79]
After sorted: [12, 18, 28, 36, 65, 65, 79, 82, 89, 93]

2. 插入排序

原理

將一個元素插入到一個已排好序的序列中:將第1個元素和第2個元素進行比較,將較大的元素放到右邊,這樣就有了一個有2個元素的已排好序(升序)的序列,接著將第3個元素依次與它前面的第2個和第1個元素進行比較,並插入到合適的位置,這樣就成了一個有3個元素的已排好序(升序)的序列,以此類推,直到將最後一個元素也插入到前面已排好序的序列中時,就形成了一個升序的序列。

示例程式碼

import random


def insertion_sort(array):
    for i in range(1, len(array)):
        for j in range(i, 0, -1):
            if array[j] < array[j-1]:
                array[j-1], array[j] = array[j], array[j-1]


if __name__ == '__main__':
    # 產生一個有10個元素的亂序的列表
    random_array = list(random.randint(1, 100) for _ in range(10))
    print(f'Before sorted: {random_array}')
    insertion_sort(random_array)
    print(f'After sorted: {random_array}')
Before sorted: [95, 17, 19, 19, 83, 88, 55, 65, 45, 7]
After sorted: [7, 17, 19, 19, 45, 55, 65, 83, 88, 95]

3. 選擇排序

原理

選擇序列中的最小值和序列最前面的元素交換位置:第一次遍歷第0到第n個元素,找到最小值後(如果該最小值不是第0個元素),就將該元素和第0個元素互換位置,如此之後第0個元素就是序列中值最小的元素了,接下來遍歷第1到第n個元素,找到最小值後(如果該最小值不是第1個元素),就將該元素和第1個元素互換位置,如此之後第0個元素和第1個元素組成的序列就是一個升序的序列了,以此類推,將後面的元素都遍歷完後,整個序列就變成一個升序的序列了。

示例程式碼

import random


def select_sort(array):
    for i in range(0, len(array) - 1):
        min_index = i
        for j in range(i + 1, len(array)):
            if array[j] < array[min_index]:
                min_index = j

        if i != min_index:
            array[i], array[min_index] = array[min_index], array[i]


if __name__ == '__main__':
    # 產生一個有10個元素的亂序的列表
    random_array = list(random.randint(1, 100) for _ in range(10))
    print(f'Before sorted: {random_array}')
    select_sort(random_array)
    print(f'After sorted: {random_array}')
Before sorted: [97, 34, 65, 1, 4, 35, 13, 40, 14, 84]
After sorted: [1, 4, 13, 14, 34, 35, 40, 65, 84, 97]

4. 希爾排序

原理

希爾排序其實也是一種插入排序,是本文介紹的第2種簡單插入排序的改進版本,在希爾排序中,“段”(也叫增量)的概念很重要:每次遍歷序列時將序列分成等長的“段”,遍歷完成後保證在每段中相同位置的元素都滿足前一段的元素小於後一段的元素,相當於每段中相同位置的元素就構成了一個升序的序列了,段的長度按照 step = len(array) // 2; step //= 2 分割,直到最後step為1時,也滿足每個段的相同位置,前一段的元素都小於後一段的元素,因為每段序列中就只有1個元素,所以此序列就自然而然變成了一個升序的序列。

示例程式碼

import random


def shell_sort(array):
    step = len(array) // 2
    while step > 0:
        # 從最後一段step開始往前遍歷
        for i in range(step, len(array)):
            value = array[i]
            # j表示前一段step的座標
            j = i - step
            # 依次往前遍歷每一段step,並把大於最後一段value的元素依次往後挪
            while j >= 0 and array[j] > value:
                array[j+step] = array[j]
                j -= step
            array[j+step] = value
        step //= 2


if __name__ == '__main__':
    # 產生一個有10個元素的亂序的列表
    random_array = list(random.randint(1, 100) for _ in range(10))
    print(f'Before sorted: {random_array}')
    shell_sort(random_array)
    print(f'After sorted: {random_array}')
Before sorted: [51, 84, 42, 56, 65, 61, 25, 1, 29, 40]
After sorted: [1, 25, 29, 40, 42, 51, 56, 61, 65, 84]

5. 快速排序

原理

選取一個元素(通常就是序列最前端的那個元素),將其他比它小的元素放都到該元素前面,比它大的元素都放到該元素後面,這樣下來,以該元素為中心點的“前”、“中”、“後”就形成了一個升序的序列,再以遞迴的方式將該中心點兩邊的序列以同樣的方式進行“排序”,最後得到的序列整體就自然而然變成一個升序的序列了。

示例程式碼

import random


def quick_sort(array, low, high):
    if low >= high:
        return

    pivot_index = low
    pivot = array[pivot_index]
    for i in range(low + 1, high + 1):
        if array[i] < pivot:
            pivot_index += 1
            array[i], array[pivot_index] = array[pivot_index], array[i]

    array[low], array[pivot_index] = array[pivot_index], array[low]

    quick_sort(array, low, pivot_index - 1)
    quick_sort(array, pivot_index + 1, high)


if __name__ == '__main__':
    # 產生一個有10個元素的亂序的列表
    random_array = list(random.randint(1, 100) for _ in range(10))
    print(f'Before sorted: {random_array}')
    quick_sort(random_array, 0, len(random_array) - 1)
    print(f'After sorted: {random_array}')
Before sorted: [18, 45, 50, 20, 84, 28, 51, 29, 87, 86]
After sorted: [18, 20, 28, 29, 45, 50, 51, 84, 86, 87]

6. 歸併排序

原理

分治策略(分而治之):使用 二分法遞迴 不斷將序列進行分割,直至分割到最小片段(只有兩個元素),然後對二分後的兩個片段進行排序,排好序之後再回到遞迴的上一層進行重複的操作即可(每一次遞迴只是片段的位置或長度不同而已,操作方法都是一樣的)。

示例程式碼

import random


def merge_sort(array, low, high):
    """使用二分法將序列不斷進行分割,直至分割為最小片段(只有兩個元素),
    然後將片段進行排序"""
    mid = (low + high) // 2
    if low < high:
        merge_sort(array, low, mid)
        merge_sort(array, mid + 1, high)
        merge(array, low, mid, high)

        # 這裡只是為了更直觀的展示排序過程,如果有需要,可以新增列印mid的值
        print(low, high, array)

    return array


def merge(array, low, mid, high):
    """排序:以mid為分界點遍歷並對比兩邊的值,然後形成一個有序的臨時序列,
    最後再賦值回原序列"""
    temp_array = [None] * (high - low + 1)
    i = low
    j = mid + 1
    k = 0
    while i <= mid and j <= high:
        if array[i] < array[j]:
            temp_array[k] = array[i]
            k += 1
            i += 1
        else:
            temp_array[k] = array[j]
            k += 1
            j += 1

    # 將另一個片段中剩餘的元素放入臨時陣列中
    # 下面的兩個while,每次只會有一個被執行,因為前面的while中i或j一定會有一個達到上限
    while i <= mid:
        temp_array[k] = array[i]
        k += 1
        i += 1

    while j <= high:
        temp_array[k] = array[j]
        k += 1
        j += 1

    for x in range(len(temp_array)):
        array[x + low] = temp_array[x]


if __name__ == '__main__':
    # 產生一個有10個元素的亂序的列表
    random_array = list(random.randint(1, 100) for _ in range(10))
    print(f'Before sorted: {random_array}')
    merge_sort(random_array, 0, len(random_array) - 1)
    print(f'After sorted: {random_array}')
Before sorted: [65, 77, 33, 17, 30, 26, 76, 41, 38, 85]
0 1 [65, 77, 33, 17, 30, 26, 76, 41, 38, 85]
0 2 [33, 65, 77, 17, 30, 26, 76, 41, 38, 85]
3 4 [33, 65, 77, 17, 30, 26, 76, 41, 38, 85]
0 4 [17, 30, 33, 65, 77, 26, 76, 41, 38, 85]
5 6 [17, 30, 33, 65, 77, 26, 76, 41, 38, 85]
5 7 [17, 30, 33, 65, 77, 26, 41, 76, 38, 85]
8 9 [17, 30, 33, 65, 77, 26, 41, 76, 38, 85]
5 9 [17, 30, 33, 65, 77, 26, 38, 41, 76, 85]
0 9 [17, 26, 30, 33, 38, 41, 65, 76, 77, 85]
After sorted: [17, 26, 30, 33, 38, 41, 65, 76, 77, 85]

7. 堆排序

堆排序其實並不複雜,只要瞭解了對應的堆資料結構知識,程式碼其實就很簡單了,不然光看程式碼是很難理解的,堆排序需要用到大頂堆或小頂堆的資料結構。

7.1 陣列與堆

堆的定義:堆是一種完全二叉樹的資料結構,若二叉樹中每個節點的值都大於或等於其左右子節點的值,則稱之為大頂堆,大頂堆的根節點為整個二叉樹中的最大值;而相反,若二叉樹中每個節點的值都小於或等於其左右子節點的值,則稱之為小頂堆,小頂堆的根節點為整個二叉樹中的最小值。

完全二叉樹中編號為i的左子節點編號為 2i+1 ,右子節點的編號為 2i+2 ,所以用陣列表示的堆應該滿足:

array[i] >= array[2i+1] 且 array[i] >= array[2i+2],即大頂堆

array[i] <= array[2i+1] 且 array[i] <= array[2i+2],即小頂堆

7.2 堆排序

原理

以大頂堆為例:將一個數組構造成一個大頂堆之後,其根節點就是整個陣列最大值(此時整個陣列的最大元素已找出),然後將其與尾部元素(最後一個編號的節點)進行交換,然後將除了尾部元素的其他節點再次構造成一個新的大頂堆,那麼這個新的大頂堆的根節點元素就是整個陣列的第二大元素,然後再次將它與自己的尾部元素進行交換,以此類推,將所有堆的最大元素依次往後放,最後形成的就是一個升序的陣列了。

示例程式碼

import random


def heap_sort(array):
    """使用大頂堆進行排序"""

    # 構造大頂堆:調整陣列,使之符合大頂堆的資料結構
    for i in range(len(array) // 2 - 1, -1, -1):
        adjust(array, i, len(array))

    for i in range(len(array) - 1, -1, -1):
        # 將堆的根節點元素與堆的尾部元素交換
        array[0], array[i] = array[i], array[0]
        # 將除了尾部元素之外的元素作為新的堆進行調整,形成新的大頂堆
        adjust(array, 0, i)


def adjust(array, pos, length):
    """調整節點值,使之符合大頂堆的資料結構特點"""

    # 左子節點的下標
    child = pos * 2 + 1
    if child + 1 < length and array[child] < array[child + 1]:
        child += 1

    if child < length and array[pos] < array[child]:
        array[pos], array[child] = array[child], array[pos]
        # 繼續調整其中一個子節點
        adjust(array, child, length)


if __name__ == '__main__':
    # 產生一個有10個元素的亂序的列表
    random_array = list(random.randint(1, 100) for _ in range(10))
    print(f'Before sorted: {random_array}')
    heap_sort(random_array)
    print(f'After sorted: {random_array}')
Before sorted: [7, 91, 54, 42, 32, 43, 82, 81, 43, 2]
After sorted: [2, 7, 32, 42, 43, 43, 54, 81, 82, 91]

8. 基數排序

原理

核心思想:基數排序的思想其實很簡單,大家平時可能都有想到過,只不過具體的實現方法(演算法)可能沒有基數排序這麼好,數字的位數越多,則這個數字就越大,如果位數相同,那麼高位的數字越大,則這個數字就越大。是不是很簡單!其實程式碼邏輯也不難,讀一遍然後稍微想一下就可以懂了。

示例程式碼

import random


def basic_sort(array):
    # 找到陣列中的最大值
    max_value = 0
    for i in range(len(array)):
        if array[i] > max_value:
            max_value = array[i]

    # 計算最大值的位數,也是之後要遍歷計算的次數
    times = 0
    while max_value > 0:
        max_value //= 10
        times += 1

    arrays = [[] for _ in range(10)]
    for i in range(times):
        # 根據i位上的數字放入對應的子陣列中
        for j in range(len(array)):
            x = array[j] % 10 ** (i + 1) // 10 ** i
            arrays[x].append(array[j])

        # 將子陣列中的數字放回原陣列array中,
        # 此時的array已是按i位的數字排好序了
        count = 0
        for sub_array_index in range(10):
            while len(arrays[sub_array_index]) > 0:
                array[count] = arrays[sub_array_index].pop(0)
                count += 1


if __name__ == '__main__':
    # 產生一個有10個元素的亂序的列表
    random_array = list(random.randint(1, 100) for _ in range(10))
    print(f'Before sorted: {random_array}')
    basic_sort(random_array)
    print(f'After sorted: {random_array}')
Before sorted: [22, 100, 43, 88, 18, 57, 97, 24, 91, 69]
After sorted: [18, 22, 24, 43, 57, 69, 88, 91, 97, 100]