算法複雜度分爲時間複雜度和空間複雜度。其中, 時間複雜度是指執行算法所須要的計算工做量;而空間複雜度是指執行這個算法所須要的內存空間。算法
算法的複雜性體如今運行該算法時的計算機所需資源的多少上,計算機資源最重要的是時間和空間資源,所以複雜度分爲時間和空間複雜度。用大O表示。shell
常見的時間複雜度(按效率排序)app
冒泡法:第一趟:相鄰的兩數相比,大的往下沉。最後一個元素是最大的。ui
第二趟:相鄰的兩數相比,大的往下沉。最後一個元素不用比。spa
1 def bubble_sort(array): 2 for i in range(len(array)-1): 3 for j in range(len(array) - i -1): 4 if array[j] > array[j+1]: 5 array[j], array[j+1] = array[j+1], array[j]
時間複雜度:O(n^2)code
穩定性:穩定blog
改進:若是一趟比較沒有發生位置變換,則認爲排序完成排序
1 def bubble_sort(array): 2 for i in range(len(array)-1): 3 current_status = False 4 for j in range(len(array) - i -1): 5 if array[j] > array[j+1]: 6 array[j], array[j+1] = array[j+1], array[j] 7 current_status = True 8 if not current_status: 9 break
選擇排序法:每一次從待排序的數據元素中選出最小(或最大)的一個元素,存放到序列的起始位置,直到所有排完。
1 def select_sort(array): 2 for i in range(len(array)-1): 3 min = i 4 for j in range(i+1, len(array)): 5 if array[j] < array[min]: 6 min = j 7 array[i], array[min] = array[min], array[i]
時間複雜度:O(n^2)遞歸
穩定性:不穩定索引
列表被分爲有序區和無序區兩個部分。最初有序區只有一個元素。
每次從無序區選擇一個元素,插入到有序區的位置,直到無序區變空。
其實就至關於摸牌:
1 def insert_sort(array): 2 # 循環的是第二個到最後(待摸的牌) 3 for i in range(1, len(array)): 4 # 待插入的數(摸上來的牌) 5 min = array[i] 6 # 已排好序的最右邊一個元素(手裏的牌的最右邊) 7 j = i - 1 8 # 一隻和排好的牌比較,排好的牌的牌的索引必須大於等於0 9 # 比較過程當中,若是手裏的比摸上來的大, 10 while j >= 0 and array[j] > min: 11 # 那麼手裏的牌往右邊移動一位,就是把j付給j+1 12 array[j+1] = array[j] 13 # 換完之後在和下一張比較 14 j -= 1 15 # 找到了手裏的牌比摸上來的牌小或等於的時候,就把摸上來的放到它右邊 16 array[j+1] = min
時間複雜度:O(n^2)
穩定性:穩定
取一個元素p(一般是第一個元素,可是這是比較糟糕的選擇),使元素p歸位(把p右邊比p小的元素都放在它左邊,在把空缺位置的左邊比p大的元素放在p右邊);
列表被p分紅兩部分,左邊都比p小,右邊都比p大;
遞歸完成排序。
1 def quick_sort(array, left, right): 2 if left < right: 3 mid = partition(array, left, right) 4 quick_sort(array, left, mid-1) 5 quick_sort(array, mid+1, right) 6 7 def partition(array, left, right): 8 tmp = array[left] 9 while left < right: 10 while left < right and array[right] >= tmp: 11 right -= 1 12 array[left] = array[right] 13 while left < right and array[left] <= tmp: 14 left += 1 15 array[right] = array[left] 16 array[left] = tmp 17 return left
時間複雜度:O(nlogn),通常狀況是O(nlogn),最壞狀況(逆序):O(n^2)
穩定性:不穩定
特色:就是快
步驟:
創建堆
獲得堆頂元素,爲最大元素
去掉堆頂,將堆最後一個元素放到堆頂,此時可經過一次調整從新使堆有序。
堆頂元素爲第二大元素。
重複步驟3,直到堆變空。
1 def sift(array, left, right): 2 """調整""" 3 i = left # 當前調整的小堆的父節點 4 j = 2*i + 1 # i的左孩子 5 tmp = array[i] # 當前調整的堆的根節點 6 while j <= right: # 若是孩子還在堆的邊界內 7 if j < right and array[j] < array[j+1]: # 若是i有右孩子,且右孩子比左孩子大 8 j = j + 1 # 大孩子就是右孩子 9 if tmp < array[j]: # 比較根節點和大孩子,若是根節點比大孩子小 10 array[i] = array[j] # 大孩子上位 11 i = j # 新調整的小堆的父節點 12 j = 2*i + 1 # 新調整的小堆中I的左孩子 13 else: # 不然就是父節點比大孩子大,則終止循環 14 break 15 array[i] = tmp # 最後i的位置因爲是以前大孩子上位了,是空的,而這個位置是根節點的正確位置。 16 17 18 def heap(array): 19 n = len(array) 20 # 建堆,從最後一個有孩子的父親開始,直到根節點 21 for i in range(n//2 - 1, -1, -1): 22 # 每次調整i到結尾 23 sift(array, i, n-1) 24 # 挨個出數 25 for i in range(n-1, -1, -1): 26 # 把根節點和調整的堆的最後一個元素交換 27 array[0], array[i] = array[i], array[0] 28 # 再調整,從0到i-1 29 sift(array, 0, i-1)
時間複雜度:O(nlogn),
穩定性:不穩定
特色:一般都比快排慢
回顧一下堆排的過程:
1. 創建最大堆(堆頂的元素大於其兩個兒子,兩個兒子又分別大於它們各自下屬的兩個兒子... 以此類推)
2. 將堆頂的元素和最後一個元素對調(至關於將堆頂元素(最大值)拿走,而後將堆底的那個元素補上它的空缺),而後讓那最後一個元素從頂上往下滑到恰當的位置(從新使堆最大化)。
3. 重複第2步。
這裏的關鍵問題就在於第2步,堆底的元素確定很小,將它拿到堆頂和本來屬於最大元素的兩個子節點比較,它比它們大的可能性是微乎其微的。實際上它確定小於其中的一個兒子。而大於另外一個兒子的可能性很是小。因而,這一次比較的結果就是機率不均等的,根據前面的分析,機率不均等的比較是不明智的,由於它並不能保證在糟糕狀況下也能將問題的可能性削減到本來的1/2。能夠想像一種極端狀況,若是a確定小於b,那麼比較a和b就會什麼信息也得不到——本來剩下多少可能性仍是剩下多少可能性。
在堆排裏面有大量這種近乎無效的比較,由於被拿到堆頂的那個元素幾乎確定是很小的,而靠近堆頂的元素又幾乎確定是很大的,將一個很小的數和一個很大的數比較,結果幾乎確定是「小於」的,這就意味着問題的可能性只被排除掉了很小一部分。
這就是爲何堆排比較慢(堆排雖然和快排同樣複雜度都是O(NlogN)但堆排複雜度的常係數更大)。
MacKay也提供了一個修改版的堆排:每次不是將堆底的元素拿到上面去,而是直接比較堆頂(最大)元素的兩個兒子,即選出次大的元素。因爲這兩個兒子之間的大小關係是很不肯定的,二者都很大,說很差哪一個更大哪一個更小,因此此次比較的兩個結果就是機率均等的了
思路:
一次歸併:將現有的列表分爲左右兩段,將兩段裏的元素逐一比較,小的就放入新的列表中。比較結束後,新的列表就是排好序的。
而後遞歸。
1 # 一次歸併 2 def merge(array, low, mid, high): 3 """ 4 兩段須要歸併的序列從左往右遍歷,逐一比較,小的就放到 5 tmp裏去,再取,再比,再放。 6 """ 7 tmp = [] 8 i = low 9 j = mid +1 10 while i <= mid and j <= high: 11 if array[i] <= array[j]: 12 tmp.append(array[i]) 13 i += 1 14 else: 15 tmp.append(array[j]) 16 j += 1 17 while i <= mid: 18 tmp.append(array[i]) 19 i += 1 20 while j <= high: 21 tmp.append(array[j]) 22 j += 1 23 array[low:high+1] = tmp 24 25 def merge_sort(array, low, high): 26 if low < high: 27 mid = (low + high) // 2 28 merge_sort(array, low, mid) 29 merge_sort(array, mid+1, high) 30 merge(array, low, mid, high)
時間複雜度:O(nlogn)
穩定性:穩定
快排、堆排和歸併的小結
三種排序算法的時間複雜度都是O(nlogn)
通常狀況下,就運行時間而言:
快速排序 < 歸併排序 < 堆排序
三種排序算法的缺點:
快速排序:極端狀況下排序效率低
歸併排序:須要額外的內存開銷
堆排序:在快的排序算法中相對較慢
希爾排序是一種分組插入排序算法。
首先取一個整數d1=n/2,將元素分爲d1個組,每組相鄰量元素之間距離爲d1,在各組內進行直接插入排序;
取第二個整數d2=d1/2,重複上述分組排序過程,直到di=1,即全部元素在同一組內進行直接插入排序。希爾排序每趟並不使某些元素有序,而是使總體數據愈來愈接近有序;最後一趟排序使得全部數據有序。
1 def shell_sort(li): 2 """希爾排序""" 3 gap = len(li) // 2 4 while gap > 0: 5 for i in range(gap, len(li)): 6 tmp = li[i] 7 j = i - gap 8 while j >= 0 and tmp < li[j]: 9 li[j + gap] = li[j] 10 j -= gap 11 li[j + gap] = tmp 12 gap //= 2
時間複雜度:O((1+τ)n)
不是很快,位置尷尬