1. 程式人生 > >十大經典排序演算法 講解,python3實現

十大經典排序演算法 講解,python3實現

接受各位指出的錯誤

重點推薦!!!

這個網址可以看到各個演算法的執行的直觀過程,找到sort
勉強推薦這個吧,前面的幾個演算法圖解還好,後面的幾個就不好了

演算法概述

這部分內容來自這麼大牛

演算法分類

十種常見排序演算法可以分為兩大類:
非線性時間比較類排序:通過比較來決定元素間的相對次序,由於其時間複雜度不能突破O(nlogn),因此稱為非線性時間比較類排序。

線性時間非比較類排序:不通過比較來決定元素間的相對次序,它可以突破基於比較排序的時間下界,以線性時間執行,因此稱為線性時間非比較類排序。
在這裡插入圖片描述

演算法複雜度

這個圖不是很好,維基百科給的圖解非常好


在這裡插入圖片描述

相關概念

穩定:如果a原本在b前面,而a=b,排序之後a仍然在b的前面。

不穩定:如果a原本在b的前面,而a=b,排序之後 a 可能會出現在 b 的後面。

時間複雜度:對排序資料的總的操作次數。反映當n變化時,操作次數呈現什麼規律。

空間複雜度:是指演算法在計算機內執行時所需儲存空間的度量,它也是資料規模n的函式。

基於比較的排序演算法

氣泡排序(Bubble Sort)

演算法思想

氣泡排序演算法的運作如下:
比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最後一對。這步做完後,最後的元素會是最大的數。
針對所有的元素重複以上的步驟,除了最後一個。
持續每次對越來越少的元素重複上面的步驟,直到沒有任何一對數字需要比較。

python3 實現

    def bubble_sort(nums,reverse=False):
        '''
        氣泡排序
        其時間複雜度是:氣泡排序的時間複雜度為O(n^2)。
        :param nums: 一個list
        :return: 無需返回 已經在原來的陣列上進行修改
        '''
        for i in range(len(nums)):
            for j in range(0,len(nums)-i-1):
                if reverse:
                    if
nums[j]<nums[j+1]: tmp=nums[j+1] nums[j+1]=nums[j] nums[j]=tmp else: if nums[j]>nums[j+1]: tmp=nums[j+1] nums[j+1]=nums[j] nums[j]=tmp

選擇排序(Selection Sort)

演算法思想

首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,
再從剩餘未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾。
以此類推,直到所有元素均排序完畢。

選擇排序的主要優點與資料移動有關。如果某個元素位於正確的最終位置上,則它不會被移動。選擇排序每次交換一對元素,它們當中至少有一個將被移到其最終位置上,因此對 n個元素的表進行排序總共進行至多 n-1次交換。在所有的完全依靠交換去移動元素的排序方法中,選擇排序屬於非常好的一種。

python3 實現

    def select_sort(nums,reverse=False):
        '''
        選擇排序
        其時間複雜度是:O(n^2)。
        :param nums: 一個list
        :return: 無需返回 已經在原來的陣列上進行修改
        '''
        for i in range(len(nums)-1):
            index = i
            for j in range(i+1,len(nums)):
                if reverse:
                    if nums[j] > nums[index]:
                        index = j
                else:
                    if nums[j]<nums[index]:
                        index=j

            if index!=i:
                tmp=nums[i]
                nums[i]=nums[index]
                nums[index]=tmp

插入排序(Insertion Sort)

演算法思想

插入排序(英語:Insertion Sort)是一種簡單直觀的排序演算法。它的工作原理是通過構建有序序列,對於未排序資料,在已排序序列中從後向前掃描,找到相應位置並插入。

設有一組關鍵字{K1, K2,…, Kn};
排序開始就認為 K1 是一個有序序列;
讓 K2 插入上述表長為 1 的有序序列,使之成為一個表長為 2 的有序序列;
然後讓 K3 插入上述表長為 2 的有序序列,使之成為一個表長為 3 的有序序列;
依次類推,最後讓 Kn 插入上述表長為 n-1 的有序序列,得一個表長為 n 的有序序列。

具體演算法描述如下:
從第一個元素開始,該元素可以認為已經被排序
取出下一個元素,在已經排序的元素序列中從後向前掃描
如果該元素(已排序)大於新元素,將該元素移到下一位置
重複步驟 3,直到找到已排序的元素小於或者等於新元素的位置
將新元素插入到該位置後
重複步驟 2~5

    時間複雜度是:O(n^2)。

python3實現

    def insert_sort(nums,reverse=False):
        '''
        :param nums: 一個list
        :return: 無需返回 已經在原來的陣列上進行修改
        '''
        for i in range(1,len(nums)):
            for j in range(i-1,-1,-1):
                if reverse:
                    if nums[j]<nums[j+1]:
                        tmp=nums[j+1]
                        nums[j+1]=nums[j]
                        nums[j]=tmp

                    else:
                        break
                else:
                    if nums[j]>nums[j+1]:
                        tmp = nums[j + 1]
                        nums[j + 1] = nums[j]
                        nums[j] = tmp
                    else:
                        break

希爾排序(Shell Sort)

演算法思想

希爾排序的實質就是分組插入排序,該方法又稱縮小增量排序
基本思想是:
將整個待排元素序列分割成若干個子序列(由相隔某個“增量”的元素組成的)分別進行直接插入排序
然後依次縮減增量再進行排序,待整個序列中的元素基本有序(增量足夠小)時
再對全體元素進行一次直接插入排序。因為直接插入排序在元素基本有序的情況下(接近最好情況),效率是很高的,因此希爾排序在時間效率上比前兩種方法有較大提高。

演算法思路:
先取一個正整數 d1(d1 < n),把全部記錄分成 d1 個組,所有距離為 d1 的倍數的記錄看成一組,然後在各組內進行插入排序
然後取 d2(d2 < d1)
重複上述分組和排序操作;直到取 di = 1(i >= 1) 位置,即所有記錄成為一個組,最後對這個組進行插入排序。一般選 d1 約為 n/2,d2 為 d1 /2, d3 為 d2/2 ,…, di = 1。

python3 實現

    def shell_sort(nums,reverse=False):
        '''
        :param nums: 一個list
        :param reverse:
        :return: 無需返回 已經在原來的陣列上進行修改
        '''
        step=len(nums)//2 #注意 取商
        while step>0:
            for i in range(step,len(nums)):#這裡的述寫方法是沒錯的 不理解的可以去這篇部落格找解答
                j=i-step
                while j>=0:
                    if reverse:
                        if nums[j+step] > nums[j]:
                            tmp=nums[j]
                            nums[j]=nums[j+step]
                            nums[j+step]=tmp
                            j-=step
                        else:
                            break
                    elif nums[j+step] < nums[j]:
                            tmp=nums[j]
                            nums[j]=nums[j+step]
                            nums[j+step]=tmp
                            j-=step
                    else:
                        break

            step=step//2

歸併排序(Merge Sort)

演算法思路

歸併排序(MERGE-SORT)是建立在歸併操作上的一種有效的排序演算法,
該演算法是採用分治法(Divide and Conquer)的一個非常典型的應用。將已有序的子序列合併,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱為二路歸併。

python3實現

    def merge_sort(nums,reverse=False):
        '''

        :param nums:
        :param reverse:
        :return: 返回已經排序好的陣列
        '''
        def merge(l_list,r_list):
            res=[]
            i=0
            j=0
            while i<len(l_list) and j<len(r_list):
                if reverse:
                    if l_list[i]>r_list[j]:
                        res.append(l_list[i])
                        i+=1
                    else:
                        res.append(r_list[j])
                        j+=1
                else:
                    if l_list[i]<r_list[j]:
                        res.append(l_list[i])
                        i+=1
                    else:
                        res.append(r_list[j])
                        j+=1

            if i<len(l_list):
                res.extend(l_list[i:])
            if j<len(r_list):
                res.extend(r_list[j:])
            return res

        if len(nums)<=1:
            return nums
        mid=len(nums)//2
        l_nums=MergeSort.merge_sort(nums[:mid],reverse)
        r_nums=MergeSort.merge_sort(nums[mid:],reverse)
        res=merge(l_nums,r_nums)
        return res

快速排序(Quick Sort)

演算法思路

快速排序由於排序效率在同為O(N*logN)的幾種排序方法中效率較高,因此經常被採用,
快速排序是一種劃分交換排序。它採用了一種分治的策略,通常稱其為分治法
基本思想是:
1.先從數列中取出一個數作為基準數。
2.分割槽過程,將比這個數大的數全放到它的右邊,小於或等於它的數全放到它的左邊。
3.再對左右區間重複第二步,直到各區間只有一個數。
挖坑填數+分治法:

python3實現

    @staticmethod
    def quick_sort(nums,reverse=False):
        '''
        :param nums:
        :param reverse:
        :return: 在原有的nums上進行修改 沒有返回
        '''
        def quickSort(nums, left, right,reverse):
            if left >= right:
                return
            low = left
            high = right
            key = nums[low]
            while left < right:
                if reverse:
                    while left < right and nums[right] <= key:
                        right -= 1
                    nums[left] = nums[right]
                    while left < right and nums[left] > key:
                        left += 1
                    nums[right] = nums[left]
                else:
                    while left < right and nums[right] > key:
                        right -= 1
                    nums[left] = nums[right]
                    while left < right and nums[left] <= key:
                        left += 1
                    nums[right] = nums[left]
            nums[right] = key
            quickSort(nums, low, left-1,reverse) #這地方一定要注意 left-1 !!!!!!!!!!!!!!!!!!!!! 
            quickSort(nums, left + 1, high,reverse) # left + 1 這地方一定要注意!!!!!!!!!!!!!!!!!!!!!

        return quickSort(nums,0,len(nums)-1,reverse)

堆排序(Heap Sort)

演算法思路

需要知識

必須看得:!!!在學習該演算法之前一定要在這篇部落格中學習堆的相關必須知識
堆:
儲存:一般採用陣列進行儲存 是一個"近似完全二叉樹" 節點i 其父節點為(i-1)/2 左右子節點2i+1;2i+2
插入:將資料插入到最後一個
刪除:刪除第一個元素,同時將最後一個元素換到第一個個位置 再進行最大堆調整

堆排序:
最大堆調整(Max Heapify):將堆的末端子節點作調整,使得子節點永遠小於父節點
建立最大堆(Build Max Heap):將堆中的所有資料重新排序
堆排序(HeapSort):移除位在第一個資料的根節點,並做最大堆調整的遞迴運算

python3實現

    @staticmethod
    def heap_sort(nums,reverse=False):
        '''
        :param nums:
        :param reverse:
        :return: 所有的操作均在nums陣列上修改得,沒有返回
        '''
        def sift_down(root, end):
            """
            這個函式的作用       每次執行 都是為了讓這個 nums[root],插入到正確的位置
            最大堆調整:
            針對的是每個內節點 葉子節點預設都是最大堆
            然後 我們保證每個以內節點為跟的子樹都是"最大堆"
            思路:下沉

            """
            while True:
                if root > end:
                    break
                child = 2*root+1
                if child>end:
                    break
                if child+1 <= end and nums[child]<nums[child+1]:#注意這裡的是 最大堆調整
                    child+=1
                if nums[root]<nums[child]:
                    nums[root],nums[child]=nums[child],nums[root]
                    root=child
                else:
                    break

        for index in range((len(nums)-2)//2,-1,-1):#我們只需要對內節點實施 最大堆調整,葉子節點已經默認了是最大堆了
            sift_down(root=index,end=len(nums)-1)
        #這裡已經是最大堆了

        #下面是進行堆排序 我們這裡首先按升序進行排序
        for index in range(len(nums)-1,0,-1):#當剩餘一個元素的時候,我們終止
            nums[index],nums[0] = nums[0],nums[index]
            sift_down(0,index-1)

        if reverse:
            for index in range(len(nums)//2):  # 當剩餘一個元素的時候,我們終止
                nums[index], nums[len(nums)-1-index] = nums[len(nums)-1-index], nums[index]


基於非比較的排序的演算法

計數排序(Counting Sort)

演算法思想

這個演算法我認為本身是非常簡單的,我建議結合程式碼和部落格同步開,不然不好理解呢

輔助理解部落格,寫的不算太好,不過可以看看

計數排序是一種基於非比較的排序演算法,其空間複雜度和時間複雜度均為O(n+k),其中k是整數的範圍。
基於比較的排序演算法時間複雜度最小是O(nlogn)的。
注意:計數排序對於實數的排序是不可行的(部落格中有解釋的)
前面說了計數排序對於實數的排序是不可行的,這是因為我們無法根據最小值和最大值來確定陣列C的長度,比如0.1和0.2之間有無限個實數。
但是如果限制實數精度,依然是可行的,比如說陣列A中的數字只保留5位小數。
但是這已經不是實數了,相當於整數,因為相當於給原來陣列A的所有數都乘以10^5。

python3實現

    def count_sort(nums,reverse=False):
        '''
        :param nums:
        :param reverse:
        :return:
        '''
        min_value=min(nums)
        max_value=max(nums)
        C=[0]*(max_value-min_value+1)
        for token in nums:
            C[token-min_value]+=1

        i=0
        for index,num in enumerate(C):
            while num>0:
                nums[i] = min_value+index
                num-=1
                i+=1
        if reverse:
            nums.reverse()

基數排序(Radix Sort)

直觀理解

在這裡插入圖片描述
在上圖中,首先將所有待比較樹脂統一為統一位數長度,接著從最低位開始,依次進行排序。

  1. 按照個位數進行排序。
  2. 按照十位數進行排序。
  3. 按照百位數進行排序。
    排序後,數列就變成了一個有序序列。

演算法思路

基數排序 (Radix Sort) 是一種非比較型整數排序演算法
原理:
是將整數按位數切割成不同的數字,然後按每個位數分別比較。
排序過程:
將所有待比較數值(正整數)統一為同樣的數位長度,數位較短的數前面補零。
然後,從最低位開始,依次進行一次排序。這樣從最低位排序一直到最高位排序完成以後, 數列就變成一個有序序列。
基數排序法會使用到桶 (Bucket),顧名思義,將通過要比較的位(個位、十位、百位…),將要排序的元素分配至 0~9 個桶中,
藉以達到排序的作用,在某些時候,基數排序法的效率高於其它的比較性排序法。

python3實現

    def radix_sort(nums,reverse=False,radix=10):
        import math
        num_bit= math.ceil(math.log(max(nums),radix))
        for bit in range(1,num_bit+1):
            buckets = [[] for _ in range(radix)]  # 這是由基數確定的桶
            for num in nums:
                index=num%(radix**bit)//(radix**(bit-1))
                buckets[index].append(num)
            del nums[:] #注意這裡的方法 因為我們是在原陣列的基礎上進行修改的 所以 我們要保留引用地址
            for bucket in buckets:
                nums.extend(bucket)
        if reverse:
            nums.reverse()

桶排序(Bucket Sort)

演算法理解

桶排序當待排序的資料是服從均勻分佈的時候 速度是最快
桶排序的基本思想是:把陣列 arr 劃分為n個大小相同子區間(桶),每個子區間各自排序,最後合併。
計數排序是桶排序的一種特殊情況,可以把計數排序當成每個桶裡只有一個元素的情況。
具體的步驟:
1.找出待排序陣列中的最大值max、最小值min
2.我們使用 動態陣列ArrayList 作為桶,桶裡放的元素也用 ArrayList 儲存。桶的數量為(max-min)/arr.length+1
3.遍歷陣列 arr,計算每個元素 arr[i] 放的桶
4.每個桶各自排序
5.遍歷桶陣列,把排序好的元素放進輸出陣列

python3實現

    def bucket_sort(nums,step=10,reverse=False):
       '''
       :param nums: 
       :param reverse: 
       :return: 
       '''
       import math
       max_val=max(nums)
       min_val=min(nums)

       buckets_num=math.floor((max_val-min_val)/step)+1
       buckets=[[] for _ in range(buckets_num)] #這就是桶子
       for num in nums:
           index=math.floor((num-min_val)/step)
           buckets[index].append(num)

       del nums[:]
       for bucket in buckets:
           if len(bucket)>0:
               MergeSort.insert_sort(bucket)
               nums.extend(bucket)

以上所有的演算法的程式碼整合



class MergeSort:
    @staticmethod
    def bubble_sort(nums,reverse=False):
        '''
        氣泡排序
        其時間複雜度是:氣泡排序的時間複雜度為O(n^2)。
        :param nums: 一個list
        :return: 無需返回 已經在原來的陣列上進行修改
        '''
        for i in range(len(nums)):
            for j in range(0,len(nums)-i-1):
                if reverse:
                    if nums[j]<nums[j+1]:
                        tmp=nums[j+1]
                        nums[j+1]=nums[j]
                        nums[j]=tmp
                else:
                    if nums[j]>nums[j+1]:
                        tmp=nums[j+1]
                        nums[j+1]=nums[j]
                        nums[j]=tmp

    @staticmethod
    def select_sort(nums,reverse=False):
        '''
        選擇排序
        其時間複雜度是:O(n^2)。
        :param nums: 一個list
        :return: 無需返回 已經在原來的陣列上進行修改
        '''
        for i in range(len(nums)-1):
            index = i
            for j in range(i+1,len(nums)):
                if reverse