1. 程式人生 > >資料結構與算法系列9--排序演算法(冒泡、插入、選擇)

資料結構與算法系列9--排序演算法(冒泡、插入、選擇)

氣泡排序

思想:
氣泡排序只會操作相鄰的兩個資料。每次冒泡操作都會對相鄰的兩個元素進行比較,看是否滿足大小關係要求。如果不滿足就讓它倆互換。一次冒泡會讓至少一個元素移動到它應該在的位置,重複n次,就完成了n個數據的排序工作。

#優化版本
def bubbleSort(data_list):
    n=len(data_list)
    if n<=1:
        return

    for i in range(n):
        flag=False#記錄是否存在交換,提前退出冒泡迴圈的標誌位
        for j in range(n-1-i):
            if data_list[j]>data_list[j+1]:
                data_list[j],data_list[j+1]=data_list[j+1],data_list[j]
                flag=True#表示有資料交換

        if not flag:
            #如果一趟下來沒有資料交換說明已經排好序了,跳出迴圈
            print("break")
            break


aa=[5,3,7,2,9,1]
print(bubbleSort(aa))

第一,氣泡排序是原地排序演算法嗎?
冒泡的過程只涉及相鄰資料的交換操作,只需要常量級的臨時空間,所以它的空間複雜度為O(1),是一個原地排序演算法。
第二,氣泡排序是穩定的排序演算法嗎?
在氣泡排序中,只有交換才可以改變兩個元素的前後順序。為了保證氣泡排序演算法的穩定性,當有相鄰的兩個元素大小相等的時候,我們不做交換,相同大小的資料在排序前後不會改變順序,所以氣泡排序是穩定的排序演算法。
第三,氣泡排序的時間複雜度是多少?
最好情況下,要排序的資料已經是有序的了,我們只需要進行一次冒泡操作,就可以結束了,所以最好情況時間複雜度是O(n)。而最壞的情況是,要排序的資料剛好是倒序排列的,我們需要進行n次冒泡操作,所以最壞情況時間複雜度為O(n2)。
平均情況下的時間複雜度是O(n2)。

插入排序

思想:
首先,我們將陣列中的資料分為兩個區間,已排序區間和未排序區間。初始已排序區間只有一個元素,就是陣列的第一個元素。插入演算法的核心思想是取未排序區間中的元素,在已排序區間中找到合適的插入位置將其插入,並保證已排序區間資料一直有序。重複這個過程,直到未排序區間中元素為空,演算法結束。

def insertSort(data_list):
    n=len(data_list)
    if n<=1:
        return
	#初始已排序區間只有一個元素,就是陣列的第一個元素。
    for i in range(1,n):
        value=data_list[i]
        j=i-1
        #查詢插入的位置,j為在有序組中的下標,由大到小遞減
        while j>=0:
            if a[j]>values:
                a[j+1]=a[j]	#資料搬移
                j=j-1
            else:
                break
            
        data_list[j+1]=value	#插入資料

第一,插入排序是原地排序演算法嗎?
從實現過程可以很明顯地看出,插入排序演算法的執行並不需要額外的儲存空間,所以空間複雜度是O(1),也就是說,這是一個原地排序演算法。
第二,插入排序是穩定的排序演算法嗎?
在插入排序中,對於值相同的元素,我們可以選擇將後面出現的元素,插入到前面出現元素的後面,這樣就可以保持原有的前後順序不變,所以插入排序是穩定的排序演算法。
第三,插入排序的時間複雜度是多少?
如果要排序的資料已經是有序的,我們並不需要搬移任何資料。如果我們從尾到頭在有序資料組裡面查詢插入位置,每次只需要比較一個數據就能確定插入的位置。所以這種情況下,最好是時間複雜度為O(n)。注意,這裡是從尾到頭遍歷已經有序的資料。如果陣列是倒序的,每次插入都相當於在陣列的第一個位置插入新的資料,所以需要移動大量的資料,所以最壞情況時間複雜度為O(n2)。
平均情況下的時間複雜度是 O(n2)。

選擇排序

思想
選擇排序演算法的實現思路有點類似插入排序,也分已排序區間和未排序區間。但是選擇排序每次會從未排序區間中找到最小的元素,將其放到已排序區間的末尾。

# 選擇排序
def selection_sort(a: List[int]):
    if len(a) <= 1: return
    
    for i in range(len(a)):
        min_index = i
        min_val = a[i]
        for j in range(i, len(a)):
            if a[j] < min_val:
                min_val = a[j]
                min_index = j
        a[i], a[min_index] = a[min_index], a[i]

第一,選擇排序是原地排序演算法嗎?
是一種原地排序演算法。
第二,選擇排序是穩定的排序演算法嗎?
選擇排序是一種不穩定的排序演算法。從我前面畫的那張圖中,你可以看出來,選擇排序每次都要找剩餘未排序元素中的最小值,並和前面的元素交換位置,這樣破壞了穩定性。
比如5,8,5,2,9這樣一組資料,使用選擇排序演算法來排序的話,第一次找到最小元素2,與第一個5交換位置,那第一個5和中間的5順序就變了,所以就不穩定了。正是因此,相對於氣泡排序和插入排序,選擇排序就稍微遜色了。
第三,選擇排序的時間複雜度是多少?
選擇排序的最好情況時間複雜度、最壞情況和平均情況時間複雜度都為O(n2)。

對比:

氣泡排序和插入排序的時間複雜度都是O(n2),都是原地排序演算法,為什麼插入排序要比氣泡排序更受歡迎呢?
從程式碼實現上來看,氣泡排序的資料交換要比插入排序的資料移動要複雜,氣泡排序需要3個賦值操作,而插入排序只需要1個。看這段操作:

氣泡排序中資料的交換操作:
if (a[j] > a[j+1]) { // 交換
   int tmp = a[j];
   a[j] = a[j+1];
   a[j+1] = tmp;
   flag = true;
}

插入排序中資料的移動操作:
if (a[j] > value) {
  a[j+1] = a[j];  // 資料移動
} else {
  break;
}

所以,雖然氣泡排序和插入排序在時間複雜度上是一樣的,都是O(n2),但是如果我們希望把效能優化做到極致,那肯定首選插入排序。

附圖:
在這裡插入圖片描述