資料結構演算法 - 交換排序(冒泡、冒泡改進 and 快速排序)
資料結構演算法 - 交換排序(冒泡、快速排序)
-
交換排序
交換排序的核心思想是通過交換位置來對序列進行排序(其實很多排序都會用到交換操作,但它們的思想,或者說思考角度並不是交換,而只是將交換作為實現的一種手段。)
氣泡排序基本上是大學學習程式設計寫的第一個“程式”,姑且看作演算法的啟蒙,而快速排序是工業界最受歡迎的排序演算法之一,可以看做龍頭老大。這“一頭一尾”可謂將演算法設計中思想的重要性體現的淋漓盡致。
那麼同樣是基於交換思想的演算法,為什麼氣泡排序的平均時間複雜度是 ,而快速排序卻是 呢?接下來讓我們一探究竟。
-
氣泡排序
· 最優時間複雜度:
· 最壞時間複雜度:
· 平均時間複雜度:
· 空間複雜度:
· 穩定性:穩定排序基本思想是:遍歷陣列,對 相鄰 的兩個數進行比較,較大的數項表尾方向移動,而較小的數向表頭方向移動。
如果將順序表的表頭看作溫泉的池底,表位看作溫泉的頁面,那麼就相當於每次小的元素往水底移動,大的元素向頁面冒出。正所謂“冒泡”…有點牽強…“Talking is cheap,show me the code!”,直接看 Python 程式碼吧。
def bubble(array): n = len(array) # 從前向後兩兩比較,第 i 輪會將第 i 大的元素放到倒數第 i 個位置 for i in range(n): # 因為第 i 輪會確定第 i 大的元素,那麼倒數 i 個元素已經有序 # 所以後面的比較可以截止到倒數第 i 個元素為止 # 相當於前 n-i 個元素沒有經過排序,而後 i 個元素經過排序 for j in range(n-i-1): # 比較相鄰的兩個元素 if array[j] > array[j+1]: # 如果第二個數小,則交換位置 array[j], array[j+1] = array[j+1], array[j] return array
接下來對氣泡排序耗時進行計算
from random import randint import time input_data = [randint(0, 20000) for _ in range(10000)] start = time.clock() bubble(input_data) end = time.clock() # 此次排序耗費時間為: 8.10140057378438 # 通過多次執行後耗時基本在 8(+/-0.2)s print(end - start)
還有一個氣泡排序的改進版,在前 n-i 元素有序的情況下不做多餘的比較:
def bubble_improve(array): n = len(array) for i in range(n): # flag = 0 代表沒有發生過交換 flag = 0 for j in range(n-i-1): if array[j] > array[j+1]: array[j], array[j+1] = array[j+1], array[j] # 如果發生交換那麼 flag 就不為 0 flag += 1 # 內層迴圈遍歷的是未排序列,如果遍歷未排序列沒有發生交換, # 則證明沒有排序的序列已經有序,那麼可以直接退出 # 這樣的改進可以減少對已經有序的比較次數 if not flag: return return array
對此改進版本的耗時進行計算:
from random import randint import time input_data = [randint(0, 20000) for _ in range(10000)] start = time.clock() bubble_improve(input_data) end = time.clock() # 此次排序耗費時間為: 8.810628180255106 # 通過多次執行後耗時基本在 8.8(+/-0.4)s print(end - start)
說是改進,實際上增加了很多次判斷,而判斷操作也是有一定耗時的,所以“改進版”也不一定好~
-
快速排序
· 最優時間複雜度:
· 最壞時間複雜度:
· 平均時間複雜度:
· 空間複雜度:
· 穩定性:不穩定排序氣泡排序只能與相鄰的元素進行比較,那麼可不可以與非相鄰元素比較呢?答案就是快速排序。
基本思想:找元素在順序表中的正確位置,對一個順序表使用兩個指標,分別指向表頭與表尾,選取表頭的元素作為被比較元素,移動兩個指標進行比較,兩個指標重合位置即為正確位置。一趟排序後確定被比較元素的正確位置,即此元素的左面所有元素都比他小,右面所有元素都比他大。而後將兩邊的元素作為兩個順序表再使用此方法遞迴實現。
看 Python 程式碼:
def sort(array, start, end): # 判斷此陣列是否只有一個元素 if start >= end: return # start,end 記錄表頭表尾位置,low,high 進行移動 low, high = start, end # 選出被比較元素 obj = array[end] # 判斷兩指標是否重合(實際上是 high 在 low 的左面一個位置) while low < high: # 移動 low 指標,如果小於被比較元素則繼續移動 while low < high and array[low] <= obj: low += 1 # 如果大於被比較元素則將這個大的元素賦值給此時 high 所指元素 #(high 此時指示的位置的元素已經是重複元素,之前已經被換位) else: array[high] = array[low] # 移動 high指標,如果大於被比較元素則繼續移動 while low < high and array[high] > obj: high -= 1 # 如果小於被比較元素則將這個小的元素賦值給此時 low 所指元素 else: array[low] = array[high] # 將被比較元素賦值到正確位置 array[low] = obj # 根據表頭與表尾位置以及此時被比較元素的正確位置拆分順序表, # 遞迴進行以上相同的操作 sort(array, start, low-1) sort(array, low+1, end)
接下來對快速排序耗時進行計算:
from random import randint import time input_data = [randint(0, 20000) for _ in range(10000)] print(input_data) start = time.clock() sort(input_data, 0, len(input_data)-1) print(input_data) end = time.clock() # 此次排序耗費時間為: 0.02883855227136317 # 通過多次執行後耗時基本在 0.02(+/-0.01)s print(end - start)
可以看到快速排序的排序速度比氣泡排序快非常多,在 n=10000 時近乎 300 倍!這就是演算法、思想的力量!