Algorithm-sort 排序演算法 python

娃哈哈店長發表於2020-01-04

python 排序演算法:插入排序、氣泡排序、歸併排序、快速排序等。
Sorting algorithms: insert sort, bubble sort, merge sort, quick sort, etc.

排序演算法:插入排序、氣泡排序、歸併排序、快速排序等。
Sorting algorithms: insert sort, bubble sort, merge sort, quick sort, etc.

各個排序演算法圖解https://blog.csdn.net/qq_37941471/article/...

氣泡排序 Bubble Sort O(n^2) O(1)

氣泡排序(英語:Bubble Sort)是一種簡單的排序演算法。它重複地遍歷要排序的數列,一次比較兩個元素,
如果他們的順序錯誤就把他們交換過來。遍歷數列的工作是重複地進行直到沒有再需要交換,也就是說該數
列已經排序完成。

  • 比較相鄰的元素。如果第一個比第二個大(升序),就交換他們兩個。
  • 對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最後一對。這步做完後,最後的元素會是最大的數。
  • 針對所有的元素重複以上的步驟,除了最後一個。
  • 持續每次對越來越少的元素重複上面的步驟,直到沒有任何一對數字需要比較。
def bubble_sort(nums):
    for j in range(len(nums) - 1, 0, -1):  # j的取值是[len(alist)-1,len(alist)-2,.....,1]
        # j 表示每次遍歷需要比較的次數,是逐漸減小的
        for i in range(j):
            if nums[i] > nums[i + 1]:
                nums[i], nums[i + 1] = nums[i + 1], nums[i]

if __name__ == '__main__':
    nums = [54, 26, 93, 17, 77, 31, 44, 55, 20]
    bubble_sort(nums)
    print(nums)

插入排序 Insertion Sort O(n^2) O(1)

插入排序(英語:Insertion Sort)是一種簡單直觀的排序演算法。

  • 通過構建有序序列,對於未排序資料,在已排序序列中從後向前掃描,找到相應位置並插入。
  • 插入排序在實現上,在從後向前掃描過程中,需要反覆把已排序元素逐步向後挪位,為最新元素提供插入空間。
def insert_sort(nums):
    for i in range(1, len(nums)):
        for j in range(i, 0, -1):
            if nums[j] < nums[j - 1]:
                nums[j], nums[j - 1] = nums[j - 1], nums[j]

if __name__ == '__main__':
    nums = [54, 26, 93, 17, 77, 31, 44, 55, 20]
    insert_sort(alist)
    print(alist)

選擇排序 Selection sort O(n^2) O(1)

選擇排序(Selection sort)是一種簡單直觀的排序演算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素,
存放到排序序列的起始位置,然後,再從剩餘未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾。
以此類推,直到所有元素均排序完畢。

  • 選擇排序的主要優點與資料移動有關。如果某個元素位於正確的最終位置上,則它不會被移動。
  • 選擇排序每次交換一對元素,
  • 它們當中至少有一個將被移到其最終位置上,因此對n個元素的表進行排序總共進行至多n-1次交換。
  • 在所有的完全依靠交換去移動元素的排序方法中,選擇排序屬於非常好的一種。
def selection_sort1(nums):
    # 思路是將最小值逐一選擇到前面
    n = len(nums)
    for i in range(n - 1):
        min_index = i  # 記錄最小值的位置
        for j in range(i + 1, n):
            if nums[j] < nums[min_index]:
                min_index = j
        if min_index != i:
            nums[i], nums[min_index] = nums[min_index], nums[i]

def selection_sort2(nums):
    # 思路是將最大值逐一選擇到後面
    n = len(nums)
    for i in range(n - 1, 0, -1):
        max_index = i  # 記錄最大值的位置
        for j in range(i):
            if nums[j] > nums[max_index]:
                max_index = j

        if max_index != i:
            nums[i], nums[max_index] = nums[max_index], nums[i]

if __name__ == '__main__':
    nums = list(range(31,0,-1))
    selection_sort2(nums)
    print(nums)

快速排序 Quick Sort O(nlogn) O(logn)

通過一趟排序將要排序的資料分割成獨立的兩部分,其中一部分的所有資料都比另外一部分的所有資料都要小,
然後再按此方法對這兩部分資料分別進行快速排序,整個排序過程可以遞迴進行,以此達到整個資料變成有序序列。

遞迴實現

def quick_sort(nums, start, end):
    if start >= end:
        return
    pivot = nums[start]  # 基準值
    low = start  # 左指標
    high = end  # 右指標
    while low < high:
        while low < high and nums[high] >= pivot:
            high -= 1
        nums[low] = nums[high]

        while low < high and nums[low] < pivot:
            low += 1
        nums[high] = nums[low]
    nums[low] = pivot
    quick_sort(nums, start, low - 1)
    quick_sort(nums, low + 1, end)

if __name__ == '__main__':
    nums = [54, 26, 93, 17, 77, 31, 44, 55, 20]
    quick_sort(nums, 0, len(nums) - 1)
    print(nums)

歸併排序 Merge sort O(nlogn) O(logn)

將陣列array[0,...,n-1]中的元素分成兩個子陣列:array1[0,...,n/2]
和array2[n/2+1,...,n-1]。分別對這兩個陣列單獨排序,然後將已排序的
兩個子陣列歸併成一個含有n個元素的有序陣列

  • 遞迴實現
  • 在陣列長度比較短的情況下,不進行遞迴,而是選擇其他排序方案,如插入排序
  • 歸併過程中,可以用記錄陣列下標的方式代替申請新記憶體空間;從而避免array和輔助陣列間的頻繁資料移動
def merge(left, right):  # 合併兩個有序陣列
    l, r = 0, 0
    result = []
    while l < len(left) and r < len(right):
        if left[l] <= right[r]:
            result.append(left[l])
            l += 1
        else:
            result.append(right[r])
            r += 1
    result += left[l:]
    result += right[r:]
    return result

def merge_sort(nums):
    if len(nums) <= 1:
        return nums
    num = len(nums) >> 1
    left = merge_sort(nums[:num])
    right = merge_sort(nums[num:])
    return merge(left, right)

# ------------------- 按第二個改進的修改----------------------------

temp = [0] * 100

def Merge(nums, low, mid, high):
    i = low
    j = mid + 1
    size = 0
    while i <= mid and j <= high:
        if nums[i] < nums[j]:
            temp[size] = nums[i]
            i += 1
        else:
            temp[size] = nums[j]
            j += 1
        size += 1
    while i <= mid:
        temp[size] = nums[i]
        size += 1
        i += 1
    while j <= high:
        temp[size] = nums[j]
        size += 1
        j += 1
    for i in range(size):
        nums[low + i] = temp[i]

def Merge_sort(nums, low, high):
    if low >= high:
        return
    mid = (low + high) >> 1
    Merge_sort(nums, low, mid)
    Merge_sort(nums, mid + 1, high)
    Merge(nums, low, mid, high)

if __name__ == '__main__':
    nums = [54, 26, 93, 17, 77, 31, 44, 55, 20]
    Merge_sort(nums, 0, len(nums) - 1)
    print(nums)

堆排序 heap sort O(nlogn) O(logn)

堆排序是一種樹形選擇排序,是對直接選擇排序的有效改進。

  • 初始時把要排序的數的序列看作是一棵順序儲存的二叉樹,調整它們的儲存序,使之成為一個 堆,
  • 這時堆的根節點的數最大。然後將根節點與堆的最後一個節點交換。然後對前面(n-1)個數重新調整使之成為堆。
  • 依此類推,直到只有兩個節點的堆,並對 它們作交換,最後得到有n個節點的有序序列。從演算法描述來看,
  • 堆排序需要兩個過程,一是建立堆,二是堆頂與堆的最後一個元素交換位置。所以堆排序有兩個函式組成。
  • 一是建堆的滲透函式,二是反覆呼叫滲透函式實現排序的函式。

​堆的定義下:具有n個元素的序列 (h1,h2,...,hn),當且僅當滿足(hi>=h2i,hi>=2i+1)或(hi<=h2i,hi<=2i+1) (i=1,2,...,n/2)時稱之為堆。
在這裡只討論滿足前者條件的堆。由堆的定義可以看出,堆頂元素(即第一個元素)必為最大項(大頂堆)。
完全二叉樹可以很直觀地表示堆的結構。堆頂為根,其它為左子樹、右子樹。

#  調整堆,把最大值調整到堆頂
def adjust_heap(nums, i, size):
    lchild = 2 * i + 1
    rchild = 2 * i + 2
    max = i
    if i < size / 2:
        if lchild < size and nums[lchild] > nums[max]:
            max = lchild
        if rchild < size and nums[rchild] > nums[max]:
            max = rchild
        if max != i:
            nums[max], nums[i] = nums[i], nums[max]
            adjust_heap(nums, max, size)

# 建立堆
def build_heap(lists, size):
    for i in range(0, (size >> 1))[::-1]:
        adjust_heap(lists, i, size)

# 堆排序
def heap_sort(lists):
    size = len(lists)
    build_heap(lists, size)
    for i in range(0, size)[::-1]:
        lists[0], lists[i] = lists[i], lists[0]
        adjust_heap(lists, 0, i)
    return lists

if __name__ == '__main__':
    nums = [54, 26, 93, 17, 77, 31, 44, 55, 20]
    result = heap_sort(nums)
    print(result)

希爾排序 Shell Sort O(n^(1.3))) O(1)

希爾排序(Shell Sort)也是插入排序的一種。也稱為縮小增量排序,是直接插入排序演算法的一種更高效的改進版本。
希爾排序是非穩定排序演算法。該方法因DL.Shell於1959年提出而得名。

  • 將待排序列劃分為若干組,在每一組內進行插入排序,以使整個序列基本有序,然後再對整個序列進行插入排。
def shell_sort(nums):
    size = len(nums)
    gap = size >> 1
    while gap > 0:
        for i in range(gap, size):
            j = i
            while j >= gap and nums[j - gap] > nums[j]:
                nums[j - gap], nums[j] = nums[j], nums[j - gap]
                j -= gap
        gap = gap >> 1

if __name__ == '__main__':
    nums = [54, 26, 93, 17, 77, 31, 44, 55, 20]
    shell_sort(nums)
    print(nums)

基數排序 radix sort O(n+k)

基數排序是一種非比較型整數排序演算法,其原理是將整數按位數切割成不同的數字,然後按每個位數分別比較。由於整數也可以表達字串(比如名字或日期)和特定格式的浮點數,所以基數排序也不是隻能使用於整數。

  • 該演算法所花的時間基本是在把元素分配到桶裡和把元素從桶裡串起來;把元素分配到桶裡:迴圈 length 次;
    - 把元素從桶裡串起來:這個計算有點麻煩,看似兩個迴圈,其實第二迴圈是根據桶裡面的元素而定的,可以表示為:k×buckerCount;其中 k 表示某個桶中的元素個數,buckerCount  則表示存放元素的桶個數;
    - 有幾種特殊情況:
    - 第一、所有的元素都存放在一個桶內:k = length,buckerCount = 1;
    - 第二、所有的元素平均分配到每個桶中:k = length/ bukerCount,buckerCount = 10;(這裡已經固定了10個桶)
    - 所以平均情況下收集部分所花的時間為:length (也就是元素長度 n)
    - 綜上所述:時間複雜度為:posCount (length  + length) ;其中 posCount 為陣列中最大元素的最高位數;簡化下得:O( kn ) ;其中k為常數,n為元素個數;

基數排序 vs 計數排序 vs 桶排序

  1. 這三種排序演算法都利用了桶的概念,但對桶的使用方法上有明顯差異:
  2. 基數排序:根據鍵值的每位數字來分配桶;
  3. 計數排序:每個桶只儲存單一鍵值;
  4. 桶排序:每個桶儲存一定範圍的數值;

https://www.runoob.com/

def radix_sort(s):
    """基數排序"""
    i = 0 # 記錄當前正在排拿一位,最低位為1
    max_num = max(s)  # 最大值
    j = len(str(max_num))  # 記錄最大值的位數
    while i < j:
        bucket_list =[[] for _ in range(10)] #初始化桶陣列
        for x in s:
            bucket_list[int(x / (10**i)) % 10].append(x) # 找到位置放入桶陣列
        print(bucket_list)
        s.clear()
        for x in bucket_list:   # 放回原序列
            for y in x:
                s.append(y)
        i += 1

if __name__ == '__main__':
    a = [54, 26, 93, 17, 77, 31, 44, 55, 20, 2, 4]
    radix_sort(a)
    print(a)

計數排序 count Sort O(n+k)

計數排序的核心在於將輸入的資料值轉化為鍵儲存在額外開闢的陣列空間中。作為一種線性時間複雜度的排序,計數排序要求輸入的資料必須是有確定範圍的整數。

def countSort(arr):
    output = [0 for i in range(256)]
    count = [0 for i in range(256)]
    ans = ["" for _ in arr]
    for i in arr:
            count[ord(i)] += 1
    for i in range(256):
            count[i] += count[i-1]
    for i in range(len(arr)):
            output[count[ord(arr[i])]-1] = arr[i]
            count[ord(arr[i])] -= 1
    for i in range(len(arr)):
            ans[i] = output[i]
    return ans

arr = "asdfghjklpoiuytrewq"
ans = countSort(arr
print ( "字元陣列排序 %s"  %("".join(ans)) )

桶排序 bucketSort O(m),其中 m 為桶的個數

桶排序是計數排序的升級版。它利用了函式的對映關係,高效與否的關鍵就在於這個對映函式的確定。為了使桶排序更加高效,我們需要做到這兩點:

在額外空間充足的情況下,儘量增大桶的數量
使用的對映函式能夠將輸入的 N 個資料均勻的分配到 K 個桶中
同時,對於桶中元素的排序,選擇何種比較排序演算法對於效能的影響至關重要。

  1. 什麼時候最快
    當輸入的資料可以均勻的分配到每一個桶中。

  2. 什麼時候最慢
    當輸入的資料被分配到了同一個桶中。

def bucketSort(nums):
    # 選擇一個最大的數
    max_num = max(nums)
    # 建立一個元素全是0的列表, 當做桶
    bucket = [0]*(max_num+1)
    # 把所有元素放入桶中, 即把對應元素個數加一
    for i in nums:
        bucket[i] += 1

    # 儲存排序好的元素
    sort_nums = []
    # 取出桶中的元素
    for j in range(len(bucket)):
        if bucket[j] != 0:
            for y in range(bucket[j]):
                sort_nums.append(j)

    return sort_nums

nums = [5,6,3,2,1,65,2,0,8,0]
print bucketSort(nums)
本作品採用《CC 協議》,轉載必須註明作者和本文連結

文章來源首發於我的部落格Stray_Camel(^U^)ノ~YO

相關文章