歸併排序、快速排序、二路快排、三路快排python實現

源代碼:
https://github.com/lzneu/Algrithm_python

O(n*logn)級別的排序:
|-歸併排序
    分成log(n)個層級 每個層級進行O(n)排序
    每次歸併時 開闢一個新的存儲空間作爲輔助 因此需要使用O(n)多的空間
    用三個索引進行歸併 時間複雜度O(n)
    當n比較小的時候 由於複雜度前面都有常數項 因此 歸併有時比插入排序滿 可在此處進行優化
    自底向上的歸併排序 由於沒有用到數組下標 因此可以用於鏈表排序O(n*logn)
|-快速排序
    選定元素挪到正確位置,挪動的過程也是分離大於選定元素 和 小於選定元素的過程
    遞歸選定元素兩側的數組進行快排
    當集合完全有序時 快拍退化成了O(n^2)級別 這是可以通過隨機選取每次快排的樞軸來優化
|-二路快排
    當集合中的元素大量重複時 相等於temp的元素很多 導致數據集合分成了兩個不平衡的部分 ,改進patition函數的切分方式,變成雙路快排


|-三路快排
    分割時將集合分成三部分
        =temp
        <temp
        >temp
        遞歸的分割後兩個部分


總結: 歸併排序 和 快速排序 都使用了分治算法
擴展應用:
    實現方法見我的github: https://github.com/lzneu/Algrithm_python
    |- 求逆序對
        完全有序的數列 逆序對數量爲0 逆序的數列 逆序對數量最多
        可以通過求逆序對的數量來表示數列的有序程度
        暴力解法O(n^2)
        歸併排序求逆序對 O(n*logn) swap一次 說明有一個逆序對 可以疊加


    |- 求數組的第N大元素
        利用快速排序 patition只留下 符合要求的那部分 知道 temp就是第n個
        複雜度O(n)
import datetime
import random
import numpy as np
# 生成一個近乎有序的數組
def genNearlyOrderArray(n, swapTimes):
    arr = list(range(n))
    for i in range(swapTimes):
        x = random.randint(0, n)
        y = random.randint(0, n)
        swap(arr, x, y)
    return arr


def genRandomArray(n, start=0, end=10000):
    return np.random.randint(start, end, size=n)


def aTestSort(sortName, arr, n):
    t_start = datetime.datetime.now()
    sortName(arr, n)
    t_end = datetime.datetime.now()  # 記錄函數結束時間)
    long = (t_end - t_start).total_seconds()
    if isSorted(arr, n):
        print("sortName: %s, time: %f s" % (sortName.__name__, long))
    else:
        print('Sort ERROR!')


def swap(arr, i, j):
    temp = arr[i]
    arr[i] = arr[j]
    arr[j] = temp


def isSorted(arr, n):
    for i in range(n - 1):
        if (arr[i] > arr[i + 1]):
            return False
    return True


# 將arr[l...mid] 和 arr[mid+1...r]兩部分合並
def __merge(arr, l, mid, r):
    aux = arr[l: r + 1]
    i = l
    j = mid + 1
    for k in range(l, r + 1):

        if (i > mid):
            arr[k] = aux[j - l]
            j += 1
        elif (j > r):
            arr[k] = aux[i - l]
            i += 1
        elif (aux[i - l] < aux[j - l]):
            arr[k] = aux[i - l]
            i += 1
        else:
            arr[k] = aux[j - l]
            j += 1


def insertionSort4Ms(arr, l, r):
    # if l >= r:
    #     return
    for i in range(l + 1, r + 1):
        j = i
        temp = arr[i]
        while ((j > l) and (arr[j - 1] > temp)):
            arr[j] = arr[j - 1]
            j -= 1
        arr[j] = temp
    return


# 遞歸的使用歸併排序arr[l...r]
def __mergeSort(arr, l, r):
    # if l >= r:
    #     return
    # 此處優化 在n比較小時 調用插入排序
    if (r - l) <= 15:
        insertionSort4Ms(arr, l, r)
        return
    mid = int((l + r) / 2)
    __mergeSort(arr, l, mid)
    __mergeSort(arr, mid + 1, r)
    # 若有序 無需再合併了
    if (arr[mid] > arr[mid + 1]):
        __merge(arr, l, mid, r)


def mergeSort(arr, n):
    # 表示私有
    __mergeSort(arr, 0, n - 1)


# 自底向上歸併排序(由於沒有用到數組下標 因此可以用於鏈表排序)
def mergeSortBU(arr, n):
    size = 1
    while (size <= n):
        i = 0
        while (i + size < n):
            __merge(arr, i, i + size - 1, min(i + size + size - 1, n - 1))
            i += (size + size)
        size += size


# 對arr[l...r]進行partition
def __partition(arr, l, r):
    # 此處對快排進行優化,隨機中選取元素作爲樞軸
    swap(arr, l, random.randint(l, r))
    temp = arr[l]
    j = l
    for i in range(l + 1, r + 1):
        if (temp > arr[i]):
            swap(arr, j + 1, i)
            j += 1
    swap(arr, j, l)
    return j


# 對arr[l...r]進行快速排序
def __quickSort(arr, l, r):
    # if (l>=r):
    #     return  # 別忘了這個啊 要不然死循環了
    if (r - l) <= 15:
        # 元素個數少的時候用插入排序
        insertionSort4Ms(arr, l, r)
        return
    p = __partition(arr, l, r)
    __quickSort(arr, l, p - 1)
    __quickSort(arr, p + 1, r)


# 快速排序
def quickSort(arr, n):
    __quickSort(arr, 0, n - 1)


def __partition2(arr, l, r):
    swap(arr, l, random.randint(l, r))
    temp = arr[l]
    i = l + 1
    j = r
    while True:
        while (i <= r and arr[i] < temp):
            i += 1
        while (j >= l + 1 and arr[j] > temp):
            j -= 1
        if (j < i):
            break
        swap(arr, j, i)
        i += 1
        j -= 1
    # temp所在的位置是<=temp的一段 因此需要將其與j交換
    swap(arr, l, j)
    return j


def __quickSort2(arr, l, r):
    if (r - l) <= 15:
        insertionSort4Ms(arr, l, r)
        return
    p = __partition2(arr, l, r)
    __quickSort2(arr, l, p - 1)
    __quickSort2(arr, p + 1, r)


# 二路快排 將=temp的元素分散到兩個集合中,避免平衡樹不平衡
def quickSort2(arr, n):
    __quickSort2(arr, 0, n - 1)


def __partition3Ways(arr, l, r):
    swap(arr, l, random.randint(l, r))
    temp = arr[l]

    lt = l  # arr[l+1...lt] < temp
    gt = r + 1  # arr[gt...r] > temp
    i = l + 1  # arr[lt+1...i] == temp
    while (i < gt):
        # i==gt時表示已經比較結束
        if (arr[i] < temp):
            swap(arr, i, lt + 1)
            lt += 1
            i += 1
        elif (arr[i] > temp):
            swap(arr, i, gt - 1)
            gt -= 1

        else:  # arr[i] == temp
            i += 1
    swap(arr, l, lt)
    return lt, gt


def __quickSort3Ways(arr, l, r):
    if (r - l) <= 15:
        insertionSort4Ms(arr, l, r)
        return
    lt, gt = __partition3Ways(arr, l, r)
    __quickSort3Ways(arr, l, lt - 1)
    __quickSort3Ways(arr, gt, r)


def quickSort3Ways(arr, n):
    __quickSort3Ways(arr, 0, n-1)


if __name__ == '__main__':
    n = 100000
    start = 0
    end = 10000
    arr = genNearlyOrderArray(n, swapTimes=100)
    arr = genRandomArray(n, start, end)
    arr2 = arr.copy()
    arr3 = arr.copy()
    arr4 = arr.copy()
    aTestSort(mergeSort, arr, n)
    # aTestSort(quickSort, arr2, n)
    aTestSort(quickSort2, arr3, n)
    aTestSort(quickSort3Ways, arr4, n)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章