基於Python的數據結構(一)-- 排序算法

內容說明

最近在面試,在面試過程中面試官經常會要求手寫代碼。其中大都是數據結構及其查找和排序的基本算法,所以基於python-data-structure-cn這本書的內容對數據結構進行個人理解及代碼實現。由於個人的閱讀習慣原因,分享內容的順序可能與書中不一致。原書地址如下,有學習的小夥伴可以直接在線閱讀和練習。
https://facert.gitbooks.io/python-data-structure-cn

什麼是排序

排序是以某種順序從集合中放置元素的過程。對大量項進行排序可能需要大量的計算資源。排序算法的效率與正在處理的項的數量有關。對於小集合,複雜的排序方法可能更麻煩,開銷太高。另一方面,對於更大的集合,我們希望利用儘可能多的改進。
分析排序算法時,可用於分析排序過程的操作:

  1. 比較的總數 ,在進行排序的過程中,必須對兩個值進行比較,這就需要一些系統的方法來比較值。比較的總數將是測量排序過程的最常見方法;
  2. 交換的總數,當值相對於彼此不在正確的位置時,可能需要交換它們。這種交換是一種昂貴的操作,並且交換的總數對於評估算法的整體效率也將是很重要的。

下面就向大家介紹常見的幾種算法。

冒泡排序

算法原理:逐次遍歷列表元素,兩兩比較,每次將較大的值放在右邊,每次遍歷都會把最大值放在最右側,即每次遍歷的次數都減一. n,n-1,n-2…, 算法複雜度爲O(n2)。
在這裏插入圖片描述

優缺點:冒泡排序通常被認爲是最低效的排序方法,因爲它必須在最終位置被知道之前交換項。但冒泡排序具有識別排序列表和及時停止的優點。

變種:短冒泡排序–判斷是否已經完全排序,如果已經完全排序則停止遍歷。

代碼實現:

# 冒泡排序
def bubble_sort(alist):
    for i in range(len(alist)-1,0,-1):
        for j in range(i):
            if alist[j]>alist[j+1]:
                alist[j], alist[j+1] = alist[j+1],alist[j]
    return alist
# 短冒泡排序
def Shortbubblesort(alist):
    ex=True
    l=len(alist)-1
    while l>0 and ex:
        ex=False
        for i in range(l):
            if alist[i]>alist[i+1]:
                ex=True
                alist[i],alist[i+1]=alist[i+1],alist[i]
        l = l-1
    return alist

選擇排序

算法原理:對整個列表進行遍歷,每次遍歷只做一次交換,將最大值放到最右邊。遍歷n-1次排序n個項。時間複雜度O(n2)。
在這裏插入圖片描述
優缺點:與冒泡排序有相同的複雜度O(n2),然而交換數量的減少,選擇排序通常在基準研究中執行的更快。

代碼實現

# 選擇排序
def selectionsort(alist):
    for i in range(len(alist)-1,0,-1):
        lmax=i
        for j in range(i):
            if alist[j] > alist[lmax]:
                lmax=j
        alist[lmax],alist[i] = alist[i],alist[lmax]
    return alist

c=[2,4,3,5,1,8]
selectionsort(c)
print(c)               

插入排序

算法原理: 每次遍歷都是通過維護列表的子列表的排序,對新加入的元素進行插入(移位),時間複雜度也是O(n2)。
在這裏插入圖片描述
優缺點:存在n-1個遍歷以對n個元素排序。從位置 1 開始迭代並移動位置到 n-1,因爲這些是需要插回到排序子列表中的項。第 8 行執行移位操作,將值向上移動到列表中的一個位置,在其後插入。請記住,這不是像以前的算法中的完全交換。關於移位和交換的一個注意事項也很重要。通常,移位操作只需要交換大約三分之一的處理工作,因爲僅執行一次分配.在基準研究中,插入排序有非常好的性能。

代碼實現:

# 插入排序
def insertionsort(alist):
    for i in range(1,len(alist)-1):
        curvalue=alist[i]
        index=i
        while index>0 and alist[index-1]>curvalue:
            alist[index]=alist[index-1]
            index=index-1
        alist[index]=curvalue
    return alist

希爾排序(遞減遞增排序)

算法原理:將一個列表拆分爲多個較小列表來改進插入排序,每個子列表使用插入排序進行排序。選擇這些子列表的方是希爾排序的關鍵。不是將列表拆分爲連續項的子列表,而是使用增量i(或者gap),通過選擇i個項的所有項來創建子列表。最後再使用增量爲1的插入排序:即標準插入排序。在通過執行之前的子列表排序後,我們減少了將列表置於其最終順序所需的移位操作的總數。
在這裏插入圖片描述
在這裏插入圖片描述
優缺點:希爾排序在每一次的遍歷後都會產生比前一個更有序的列表,這使得最後一次的完整插入排序不需要進行更多的移位。它的時間複雜度落於O(n)與O(n2)之間的某處,比直接插入排序要快。

代碼實現

# 希爾排序
def gapInsertionSort(alist,start,gap):        # 對子列表進行插入排序
    for i in range(start+gap,len(alist),gap):
        curvalue=alist[i]
        index = i
        while index >= gap and curvalue < alist[index-gap]:
            alist[index] = alist[index-gap]
            index = index-gap
        alist[index]=curvalue
    
def ShellSort(alist):                       # 調用子列表插入排序函數,對增量進行逐次變化,以n/2爲起始,最終1爲止
    gap=len(alist)//2
    while gap > 0 :
        for start in range(gap): 
            gapInsertionSort(alist,start,gap)
        print('gap:',gap,'result:',alist)
        gap = gap//2

歸併排序

算法原理:歸併排序的原理是分而治之,將列表依次從中間切割,直到切割到單一元素,之後再根據切割的順序依次進行排序合併,最終達到排序的結果。其中實現的思想是通過遞歸進行分割和合並子列表。
在這裏插入圖片描述

優缺點:通過兩個不同的實現過程來分析。首先是將列表分爲兩半。將列表分爲一般需要logn次,其中n是列表的長度。第二個過程是合併。列表中的每個項最終被處理並放置在排列的列表上。因此,大小爲n的列表的合併操作需要n個操作。則歸併排序的時間複雜度爲O(nlogn)。但由於歸併算法需要額外的空間來保存兩個半部分,所以如果列表很大的時候會產生影響。

代碼實現

def mergesort(alist):
    print("splitting:",alist)
    if len(alist)>1:
        mid = len(alist)//2
        lefthalf=alist[:mid]
        righthalf=alist[mid:]
        
        mergesort(lefthalf)
        mergesort(righthalf)
        
        i=0
        j=0
        k=0
        
        while i < len(lefthalf) and j < len(righthalf):
            if lefthalf[i] > righthalf[j]:
                alist[k]=righthalf[j]
                j=j+1
            else:
                alist[k]=lefthalf[i]
                i=i+1
            k=k+1
            
        while i < len(lefthalf):
            alist[k]=lefthalf[i]
            i=i+1
            k=k+1
        
        while j < len(righthalf):
            alist[k]=righthalf[j]
            j=j+1
            k=k+1
    print("Merging:",alist)

快速排序

算法原理:快速排序的原理也是分而治之,但不需要額外的空間。在快速排序中增加了一個新的角色–樞軸值(pivot)。樞軸值從英文釋義上即中心,其作用是拆分列表,即列表進行拆分時的拆分點。實現過程爲:首先在列表中選擇一個元素爲樞軸點,按照樞軸值左側的值小於樞軸點,右側的值大於樞軸點的邏輯從列表兩端進行遍歷並移位,當左側標記點的位置大於右側標記點的位置時,右側標記點的位置即爲拆分位置,將拆分位置的元素與樞軸值進行交換後,即得到第一次排序。後續經過遞歸操作,對拆分點左右兩側的子列表分別進行拆分排序,最終完成整體排序。
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
優缺點:快速排序在進行拆分的時候不會佔用額外空間,如果分裂點在列表中間,則時間爲O(nlogn),但是當分裂點不在列表中間,則時間爲O(n2),所以選擇樞軸值是方法很重要的。常用的方法是中值三:考慮第一個元素,中間元素和最後一個元素。然後選擇中間值爲樞軸值。這樣將選出更好的中間值,當原列表部分有序時將特別有用。

代碼實現

def quickSort(alist):
   quickSortHelper(alist,0,len(alist)-1)

def quickSortHelper(alist,first,last):
   if first<last:

       splitpoint = partition(alist,first,last)

       quickSortHelper(alist,first,splitpoint-1)
       quickSortHelper(alist,splitpoint+1,last)


def partition(alist,first,last):
   pivotvalue = alist[first]

   leftmark = first+1
   rightmark = last

   done = False
   while not done:

       while leftmark <= rightmark and alist[leftmark] <= pivotvalue:
           leftmark = leftmark + 1

       while alist[rightmark] >= pivotvalue and rightmark >= leftmark:
           rightmark = rightmark -1

       if rightmark < leftmark:
           done = True
       else:
           temp = alist[leftmark]
           alist[leftmark] = alist[rightmark]
           alist[rightmark] = temp

   temp = alist[first]
   alist[first] = alist[rightmark]
   alist[rightmark] = temp
   return rightmark

總結

冒泡排序,選擇排序和插入排序是 O(n2)算法。
希爾排序通過排序增量子列表來改進插入排序。它落在 O(n) 和 O(n2) 之間。
歸併排序是 O(nlogn),但是合併過程需要額外的空間。
快速排序是 O(nlogn),但如果分割點不在列表中間附近,可能會降級到O(n2) 。它不需要額外的空間。

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