排序算法是面試時常考的基礎知識,今天對三個基本排序算法進行總結。
---------------------------------------------------------------------------------------------------------------
首先寫一個計算排序算法時間的裝飾器以及3個輔助函數來幫助測試排序算法性能。
裝飾器:
def timmer(func): #func 爲傳進來的排序函數對象
def warpper(arr, nums): # arr:待排序數組, nums:數組長度
start = time.time()
r = func(arr, nums)
end = time.time()
print('toatl time is :', str(end - start))
return r
return warpper
生成無序數組或基本有序數組:
def random_list(num, start, end): # 生成無序數組
res = []
for i in range(num):
res.append(random.randint(start, end))
return res
def list_near_order(nums, start): #生成基本有序數組
a = [i for i in range(start, start + nums)]
for i in range(nums//10):
ind1 = random.randint(0,nums)
ind2 = random.randint(0, nums)
a[ind1], a[ind2] = a[ind2], a[ind1]
return a
檢查排序是否正確:
def check_list(arr,ori_arr): #arr:排序後的數組, ori_arr: 原始數組
new_arr = sorted(ori_arr)
if arr == new_arr:
return True
return False
------------------------------------------------------------------------------------------------------
一、冒泡排序
基本思想:比較相鄰的兩個數,如果前者比後者大,則進行交換。每一輪排序結束,選出一個未排序中最大的數放到數組後面
冒泡排序算法的運作如下:
- 比較相鄰的元素,如果前一個比後一個大,就把它們兩個調換位置。
- 對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最後一對。這步做完後,最後的元素會是最大的數。
- 針對所有的元素重複以上的步驟,除了最後一個。
- 持續每次對越來越少的元素重複上面的步驟,直到沒有任何一對數字需要比較。
def bubble_sort(arr):
n = len(arr)
for i in range(n-1):
for j in range(1,n-i):
if arr[j] < arr[j-1]:
arr[j],arr[j-1] = arr[j-1], arr[j]
特點總結:
最差時間複雜度爲O(n^2),平均時間複雜度爲O(n^2)。穩定性:穩定。輔助空間O(1)。
算法改進:
n個元素比較n-1趟,第i趟比較n-i次
若在其中的某一趟排序中:若始終未發生元素的交換說明已經排序號好,函數結束!
def bubble_sort_pro(arr):
n = len(arr)
for i in range(n-1):
flag = 0
for j in range(1,n-i):
if arr[j] < arr[j-1]:
arr[j],arr[j-1] = arr[j-1], arr[j]
flag = 1
if flag == 0:
break
冒泡排序算法應該是我們學習的第一個排序算法,非常簡單,但是時間複雜度高,基本不會選擇排序算法來解決排序問題。
二、選擇排序
基本思想:依次選出數組最小的數放到數組的前面。首先從數組的第一個元素開始往後遍歷,找出最小的數放到第一個位置。再從剩下數組中找出最小的數放到第二個位置。以此類推,直到數組有序。
def slect_sort(arr, nums): #arr:待排序數組, nums:數組長度
for i in range(nums):
minInd = i
for j in range(i, nums):
if arr[j] < arr[minInd]:
minInd = j
arr[i], arr[minInd] = arr[minInd], arr[i]
算法特點:
時間複雜度:O(n^2)
選擇排序是不穩定的排序算法,不穩定發生在最小元素與A[i]交換的時刻。
比如序列:{ 5, 8, 5, 2, 9 },一次選擇的最小元素是2,然後把2和第一個5進行交換,從而改變了兩個元素5的相對次序。
三、插入排序
基本思想 :在要排序的一組數中,假設前面(n-1)[n>=2] 個數已經是排好順序的,現在要把第n個數找到相應位置並插入,使得這n個數也是排好順序的。如此反覆循環,直到全部排好順序。
def insert_sort(arr,nums): #arr:待排序數組, nums: 數組長度
for i in range(1, nums):
j = i - 1 # i 指向的元素爲待插入的元素, j 指向的元素爲已排好序序列的最後一個
if arr[i] < arr[j]: #若arr[i] > arr[j] 則 i 及之前的元素均爲有序,直接過, 否則找到 i 指向的元素應該在的位置
temp = arr[i]
while j >= 0 and arr[j] > temp:
arr[j+1] = arr[j]
j -= 1
arr[j+1] = temp
算法分析:
在第一趟排序中,插入排序最多比較一次,第二趟最多比較兩次,依次類推,最後一趟最多比較N-1次。因此有:
1+2+3+...+N-1 =N*(N-1)/2
因爲在每趟排序發現插入點之前,平均來說,只有全體數據項的一半進行比較,我們除以2得到:
N*(N-1)/4
複製的次數大致等於比較的次數,然而,一次複製與一次比較的時間消耗不同,所以相對於隨機數據,這個算法比冒泡排序快一倍,比選擇排序略快。
與冒泡排序、選擇排序一樣,插入排序的時間複雜度仍然爲O(N2),這三者被稱爲簡單排序或者基本排序。
如果待排序數組基本有序時,插入排序的效率會更高。
算法改進:
在插入某個元素之前需要先確定該元素在有序數組中的位置,上例的做法是對有序數組中的元素逐個掃描,當數據量比較大的時候,這是一個很耗時間的過程,可以採用二分查找法改進,這種排序也被稱爲二分插入排序。
改進後的代碼如下:
def _binary_search(arr, l, r, target): #基於二分查找來找到插入點, 但不能直接照搬二分查找的算法。 arr:數組;l,r:查找的左,右端點索引值,target:目標值
while l < r:
if r - l == 1: #當搜索範圍 r - l == 1時,開始判斷插入點的具體索引
if arr[l] > target:
return l
else:
return r
ind = (l + r)//2
if arr[ind] > target:
r = ind
else:
l = ind
def insert_sort_improve(arr, nums):
for i in range(1, nums):
j = i - 1
if arr[i] < arr[j]:
temp = arr[i]
ind = _binary_search(arr, 0, j, arr[i])
while j >= ind:
arr[j+1] = arr[j]
j -= 1
arr[ind] = temp
插入排序的改進算法還有很多,如希爾排序,2-路插入排序等。在次不再一一敘述。
插入排序,在數組基本有序的情況下,性能優異。對於較大數組的排序,一般使用快速排序與插入排序組合。可進一步提升快速排序的性能。