七種排序算法的python3實現

本文介紹七種排序算法以及Python3的實現,分別是冒泡排序,選擇排序,插入排序,希爾排序,歸併排序,堆排序以及快速排序。


1、冒泡排序

通過兩次迭代,從第一個數開始進行比較,每次將最大的數移動到最右邊,就好像氣泡從左邊移動到右邊一樣,因此獲名“冒泡”。

冒泡排序的過程如下:
1、比較相鄰的兩個元素,如果左邊大於右邊,則進行交換,否則繼續迭代
2、對每一個相鄰元素都做步驟1操作,直至一輪迭代結束,最後一個元素就是最大的數
3、針對以上未排序的部分,重複1-2步驟,每次需要排序的數列的長度逐漸減小直到變爲1

冒泡排序對n個元素,需要O(n²)的比較次數,最壞情況時,交換次數也是這麼多,因此冒泡排序是一種效率比較低的排序算法。

# -*- coding:utf-8 -*-
class BubbleSort:
    def __init__(self,m=[]):
        self.m=m
    def get_sort(self):
        return self.m

    def bubble_sort(self):
#兩次for循環,最多進行(n-1)+(n-2)+....+(2-1)次比較以及交換
       for i in range(len(self.m)):
           for j in range(len(self.m)-i-1):
               if self.m[j]>self.m[j+1]:
                   self.m[j],self.m[j+1]=self.m[j+1],self.m[j]
l=[3,5,1,99,2]
l_sort=BubbleSort(l)
l_sort.bubble_sort()
print(l_sort.get_sort())

2、選擇排序

通過兩次迭代,每次通過一次內層迭代,找到當次循環的最大值,並將其序號index記錄,並與當次循環最右邊位置的值進行交換。這樣最多經過n-1次交換,就將所有值都排好了。

與冒泡相比,二者的比較次數是相同的,但是選擇排序的交換次數大大減少。因爲對於內層迭代而言都是隻記錄而不交換,只有在外層才進行交換。
選擇排序的交換操作介於0和 (n-1)次之間。選擇排序的比較操作爲n(n-1)/2次。

選擇排序的過程如下:
1、比較相鄰的兩個元素,如果左邊大於右邊,則記錄將左邊的值賦值給index,否則繼續迭代
2、在內層迭代中重複步驟1,直到找到最大值的index
3、將index位置的值與數列的最後一個元素進行交換
4、重複1-3的步驟,直至數列的長度爲1

#-*-coding:utf-8-*-
class SelectSort:
    def __init__(self,m):
        self.m=m
    def get_sort(self):
        return self.m
    def select_sort(self):
        for  i in range(len(self.m)):
            max_index=0
            for j in range(len(self.m)-i):
                if self.m[j]>self.m[max_index]:
                    max_index=j#每次將最大的值的編號值傳給index,但不進行交換。比較的次數與冒泡相當,當時交換的次數只有n次
            self.m[len(self.m)-i-1],self.m[max_index]=self.m[max_index],self.m[len(self.m)-i-1]

l=[3,5,1,99,2]
l_sort=SelectSort(l)
l_sort.select_sort()
print(l_sort.get_sort())

3、插入排序

已經排好序的數列插入一個數,而得到一個新的有序數列。

我們得到第一個有序數列的方式,是我們將第一個元素認爲是一個數列,那麼他必然是有序的。我們只對相鄰的元素做比較,逐個推進式比較。在從後向前掃描過程中,需要反覆把已經排序的的元素逐步向後移,爲新元素提供插入空間。

插入排序的過程如下:
1、從第一個元素開始,該元素認爲已經被排序
2、取出下一個元素,在已經排序的的元素序列中從後向前掃描
3、如果該元素大於需要被排序的元素,則將該元素與需要被排序的元素交換而移動到下一位
4、重複第3步,直到在已經排序的列表中找到小於需要被排序的元素
5、將該新元素插入到該位置之後
6、重複2-5的步驟

如果目標是把n個元素的序列升序排列,那麼採用插入排序存在最好情況和最壞情況。最好情況就是,序列已經是升序排列了,在這種情況下,需要進行的比較操作需n-1次即可。最壞情況就是,序列是降序排列,那麼此時需要進行的比較共有(1/2)n(n-1)次。插入排序的賦值操作是比較操作的次數減去n-1次,(因爲n-1次循環中,每一次循環的比較都比賦值多一個,多在最後那一次比較並不帶來賦值)。平均來說插入排序算法複雜度爲O(n²)。因而,插入排序不適合對於數據量比較大的排序應用。但是,如果需要排序的數據量很小,例如,量級小於千;或者若已知輸入元素大致上按照順序排列,那麼插入排序還是一個不錯的選擇。

#-*-coding:utf-8-*-
class InsertSort:
    def __init__(self,m):
        self.m=m
    def get_sort(self):
        return self.m

    def insert_sort(self):
        for  i in range(1,len(self.m)):#默認第一個元素已經是排好序的元素,因此從第二個元素開始
            while i>=1:
                if self.m[i]<self.m[i-1]:
                  self.m[i],self.m[i-1]=self.m[i-1],self.m[i]
    # 每次插入的時候都會遍歷前面已經排好序的部分,逐個插入,直到滿足while的條件
                i-=1

l=[5,1,7,3,2,1,22,8,4,9,99,66,3]
l_sort=InsertSort(l)
l_sort.insert_sort()
print(l_sort.get_sort())

在進行向前迭代的時候當然也可以使用for,而不使用while,代碼如下:

def inset_sort(list):
    for i in range(1,len(list)):
        for j in range(i,0,-1):
            if list[j]<list[j-1]:
                list[j],list[j-1]=list[j-1],list[j]
    return list

l=[5,1,7,3,2,1,22,8,4,9,99,66,3]
print(inset_sort(l))

4、希爾排序

希爾排序也稱遞減增量排序算法,是插入排序的優化版本或者說是冒泡排序的優化版本,因爲當gap爲1的時候,實際上就變成了插入排序,在插入排序中,我們對每次比較的步長gap都是1。步長是希爾排序的核心思想,只要最終步長爲1任何步長序列都是可以的,已知的最好步長序列是由Sedgewick提出的 1,5,19,41,109,…
希爾排序最重要的地方在於當用較小步長排序後,以前用的較大步長仍然是有序的。比如,如果一個數列以步長5進行了排序然後再以步長3進行排序,那麼該數列不僅是以步長3有序,而且是以步長5有序。如果不是這樣,那麼算法在迭代過程中會打亂以前的順序,那就不會以如此短的時間完成排序了。

希爾排序的過程如下:
1、從第一個元素開始,該元素認爲已經被排序
2、設置步長值gap
3、取出下一個元素,在已經排序的元素序列中從後向前掃描
4、如果該元素大於需要被排序的元素,則將該元素與被需要排序的元素交換而移動到下一位
5、重複第4步,直到在已經排序的列表中找到小於需要被排序的元素
6、將該新元素插入到該位置之後
7、重複2-6的步驟,直到最後一次比較,也就是第二個元素與第一個元素比較
8、將gap值減半,遞歸3-7步驟,最後一次比較的時候gap=1,當gap<1時,結束遞歸

#-*-coding:utf-8-*-
class ShellSort:
    def __init__(self,m):
        self.m=m

    def get_sort(self):
        return self.m

    def shell_sort(self):
        gap=len(self.m)//2#以數列的一半爲初始的gap值
        while gap>=1:#保證gap的值始終大於1
            for i in range(0,len(self.m)-gap,gap):#每次增量爲gap,而我們在插入時候,,每次的增量爲1
                while i >=0:#爲了保證每次可以跟前面已經排序的整個數列進行比較,我們再加一個迭代的條件
                    if self.m[i]>self.m[i+gap]:
                        self.m[i],self.m[i+gap] = self.m[i+gap],self.m[i]
                    i -= gap
            gap = gap//2


l=[4,1,4,7,3,2,66,6,9,4,12,35,6899,2]
l_sort=ShellSort(l)
l_sort.shell_sort()
print(l_sort.get_sort())

5、歸併排序

歸併排序(英語:Merge sort,或mergesort),是創建在歸併操作上的一種有效的排序算法。1945年由約翰·馮·諾伊曼首次提出。該算法是採用分治法(Divide and Conquer)的一個非常典型的應用,且各層分治遞歸可以同時進行。

歸併排序是對已經排序好的數據進行排序,但是與shell的方式是不同的,歸併是實際分割了數列,產生了新的存儲空間,而shell只是概念上分割,並沒有產生新的存儲空間。
歸併算法是將數列進行逐級對半分割,一直分割到最小粒度也就是兩邊都是一個元素或者一半是一個元素,一半是兩個元素。前面shell排序也說到,如果是隻有一個元素,當然一個元素肯定是排好序的,然後將兩半的元素進行比較和交換,然後遞歸。

歸併排序的過程如下:
1、將數列對半分割成left和right兩個新的數列,而後遞歸分割,一直到left長度爲1或者 right的長度爲1
2、將left和right兩邊的數列元素從第一個開始比較,將比較小的值賦值給一個空的數組result
3、當一次比較完成後,將left或者right中剩餘的值賦值給result
4、將result的值返回給上一層遞歸函數
5、重複以上2-4的步驟

歸併算法的效率是O(n log n)

#-*-coding:utf-8-*-
class MergeSort:
    def __init__(self,m):
        self.m=m

    def get_sort(self):
        return self.merge_sort(self.m)
#使用遞歸的方式,將數列拆分,直到最小的粒度是1
    def merge_sort(self,s):
        if len(s)==1:
            return s
        else:

#將list每次截取一半出來,分別放在左右兩個list中,直至最小粒度爲1
            mid=len(s)//2
            left=self.merge_sort(s[:mid])
            right=self.merge_sort(s[mid:])

            return self.merge_list(left,right)
#排序
    def merge_list(self,left,right):
        i,j=0,0
        result=[]
#每次將left數列和right數列從第一個元素開始比較,將最小的數放在result
        while i<len(left) and j<len(right):
            if left[i]>right[j]:
                result.append(right[j])
                j+=1
            else:
                result.append(left[i])
                i+=1
#每次while循環結束以後,因爲有可能出現雖然left中已經沒有數,但是right中還有很多,當然也可能是反過來;
# 下面就是把剩下的值加入到result中,剩下的值必然是這次比較中最大的,因此都放在最右邊。
        result = result + left[i:]
        result = result + right[j:]
        return result

m=[4,1,3,99,5,22,8,5,2,1,3]
s_sort=MergeSort(m)
print(s_sort.get_sort())

6、堆排序

堆排序將一個數列構造成一個完全二叉樹,再將完全二叉樹構造成大頂堆或者小頂堆的形式。

這個時候我們的堆的根節點就是最大值所在位置,然後將該值與最後一個元素進行調換,調換之後再將堆重新構造成大頂堆,如此進行迭代就完成堆排序。

堆排序過程如下:
1、定義大頂堆構造函數build_heap,迭代調用max_heap函數,由下向上構造大頂堆。

這步並不難,方法也很多。但是如果我們想爲後來第三步做鋪墊,這步最好是有一個獨立的構造頂堆的函數max_heap

2、將大頂堆的根節點與完全二叉樹的最後一個元素交換
3、將交換過後的二叉樹進行重構大頂堆。
4、重複2-3步驟,直到最後一對元素(index分別是0和1)比較和交換

基於以上,我們也能發現,我們需要不斷利用max_heap進行大頂堆的構造。實際上我們在每次執行max_heap函數時,都是調整了一個二叉樹單分支的節點,**這個調整是從上向下的,我們需要把最大值放在最上面,因此我們藉助遞歸來完成。**當該二叉樹分支被調整後,**爲了依然保證這個分支的值依然也都滿足大頂堆的性質,我們需要向下進行遞歸來進行修正。**也就是說,**我們的數據流有兩個方向,初始時,我們通過調用max_heap從下至上遍歷每個節點來形成一個大頂堆,第二個方向是當一個節點的值被改變,我們再從該節點開始,從上至下進行遞歸修正,來保持大頂堆的形態。**簡而言之,我們的算法是迭代中包括着遞歸,每次形態被改變後,都會進行遞歸修正。
堆排序的平均時間複雜度是O(n log n)

#-*-coding:utf-8-*-
class HeapSort:
    def __init__(self,m):
        self.m=m

    def get_sort(self):
        return self.m

    def build_heap(self):
        """構建一個大頂堆或者小頂堆,這裏以大頂堆爲例"""
        """根據完全二叉樹的性質,根的index爲0。最後一個葉的根節點爲len//2-1,也就是總長的1/2減去1"""
        for i in range((len(self.m) // 2) - 1, -1, -1):#從最後一個葉的根節點開始從下往上構建大頂堆
            self.max_heap(len(self.m), i)

    def max_heap(self,heap_size,index):
        """這裏是一個獨立的,創建一個大頂堆的函數,而不涉及循環。"""
        """本函數最精巧的地方在於當一個頂堆被改變之後,下面會遞歸對這個頂堆之下的部分進行重新判斷,進而形成一個新的大頂堆的二叉樹"""
        """max_heap函數在兩個地方被調用,一個是在構建大頂堆時候,從build_heap這個入口調用;還有就是在頂堆被調整後,從heao_sort這個入口調用重新形成完整大頂堆"""
        left_child = 2 * index + 1
        right_child = left_child + 1

        if left_child < heap_size and self.m[left_child] > self.m[index]:
            largest = left_child
        else:
            largest = index
        if right_child < heap_size and self.m[right_child] > self.m[largest]:
            largest = right_child
        if largest != index:
            self.m[largest], self.m[index] = self.m[index], self.m[largest]
# 以上的代碼保證largest肯定是index,所以有了下面這個遞歸,保證每次如果二叉樹的根節點被改變,則會向下去重新比較這個分支中所有的值,而形成新的大頂堆
            self.max_heap(heap_size, largest)

    def heap_sort(self):
        """堆排序主入口"""

# 首先將列表調整爲大頂堆
        self.build_heap()
        heap_size = len(self.m)
# 調整堆中的第一個元素(也就是最大值)和最後一個元素交換,然後再將剩餘的堆進行重新調整爲大頂堆
        for i in range(len(self.m) - 1, 0, -1):
            self.m[i], self.m[0] = self.m[0], self.m[i]
            heap_size -= 1#最後一個元素在比較完之後,就是被排好序的,下面往上去接着比較和交換。
            self.max_heap( heap_size, 0)

l=[4,1,5,9,22,1,3,2,8]
l_sort=HeapSort(l)
l_sort.heap_sort()
print(l_sort.get_sort())

7、快速排序(quick_sort)

快速排序(英語:Quicksort),又稱劃分交換排序(partition-exchange sort),簡稱快排,一種排序算法,最早由東尼·霍爾提出。

快速排序是固定選擇數列中第一個值(當然你選擇別的位置的值也可以)作爲key,將比key小的值放在左邊,比key大的值放在右邊。而後進行迭代,一直到最小的部分,最後形成一個有序的數列。對於迭代而言,數組本身是不變化的,因爲雖然我們再進行多層迭代,但是我們作用的依然是一個數組,也就是說只是指針的改變,沒有新建存儲空間。

快排的過程如下:
1、選擇數列中的第一個值,賦值給key,並將數列中第一個值的下標賦值給left,最後一個值的下標賦值給right
2、定義i和j兩個參數,二者的值與left和right的值相同。
3、以i的值爲編號參數,遍歷數列的值,當對應的值小於等於key,則i的值一直增加;當i位置的值大於key,則將該值與當前j位置的值進行替換,j–
4、以j的值爲編號參數,遍歷數列的值,當對應的值大於等於key,則j的值一直減小;當j位置的值小於key,則將該值與當前i位置的值進行替換,i++
5、以上迭代結束後,i的值已經與j的值相同,這時候將key賦值給i位置的值,就完成了一輪迭代。
6、分別在當前i位置往左移動一位,往右移動一位,形成兩個新的數列,分別進行遞歸。
我們可以發現,每一輪迭代完成之後,雖然數列可能還是亂序的,但是key的位置就是他應該在的位置。

快速排序O(n log n)通常明顯比其他算法更快,因爲它的內部循環(inner loop)可以在大部分的架構上很有效率地達成。

#-*-coding:utf-8-*-
class QuickSort:
    """快排是選擇數組的第一個值的作爲中間值key,將一個數列中比其小的值放在左邊,比其大的值放在右邊"""
    """完成一次之後快排之後,再對兩邊的值進行逐次遞歸,然後得到一個有序的數列"""
    def __init__(self,m):
        self.m=m
    def get_sort(self,left,right):
        return self.quick_sort(left,right)

    def quick_sort(self,left,right):
#這裏涉及到遞歸,爲了保證left和right的值都合法,因此在被遞歸的函數的開始就進行條件限制
#如果是出現不合法的條件,那麼直接return,就進入到上一級遞歸。
        if left<0 or right<0 or right<=left:
            return self.m
        key=self.m[left]
        i,j=left,right
#在快排中多次用到了相同的限定條件,比如i<j,這是由於每次迭代雖然有包含和被包含的關係,但是在具體的迭代中,如果出現違法條件,最外層的條件是沒發限定的。
        while 0<=i<j:
#從右向左逐漸比較右邊比key小的值,發現該值就把他賦值給i所在位置的值,因爲可以確定i位置的值小於了key,所以i++,跳過目前位置
            while self.m[j]>=key and i<j:
                j-=1
            if self.m[j]<key:
                self.m[i]=self.m[j]
                i+=1
#從左向右逐漸比較左邊比key大的值,發現該值就把他賦值給j所在位置的值,因爲可以確定j位置的值大於了key,所以j--,跳過目前位置
            while self.m[i]<=key and i<j:
                i+=1
            if self.m[i]>key:
                self.m[j]=self.m[i]
                j-=1

#逐漸迭代直到最小組合
        self.m[i]=key
        self.quick_sort(left,i-1)
        self.quick_sort(i+1,right)
        return self.m

#L=[4,1,4,7,44,32,9,10]
L=[67,23,89,35,28,90,10,24]
left,right=0,len(L)-1
#L=[67,23,89,35,28,90,10,24]
L_sort=QuickSort(L)
print(L_sort.get_sort(left,right))

算法效率

借用網上的圖來列舉這七種排序算法的效率。

各種算法的效率

參考

維基百科-冒泡排序算法
維基百科-選擇排序算法
維基百科-插入排序算法
維基百科-希爾排序算法
維基百科-歸併排序算法
維基百科-堆排序算法
維基百科-快速排序算法
排序算法詳解
經典排序算法總結

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