n種排序

1 算法複雜度

算法複雜度分爲時間複雜度和空間複雜度。其中, 時間複雜度是指執行算法所需要的計算工作量;而空間複雜度是指執行這個算法所需要的內存空間。

算法的複雜性體現在運行該算法時的計算機所需資源的多少上,計算機資源最重要的是時間和空間資源,因此複雜度分爲時間和空間複雜度。用大O表示。

常見的時間複雜度(按效率排序)

2 冒泡排序

冒泡法:第一趟:相鄰的兩數相比,大的往下沉。最後一個元素是最大的。

           第二趟:相鄰的兩數相比,大的往下沉。最後一個元素不用比。

 

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)

穩定性:穩定

改進:如果一趟比較沒有發生位置變換,則認爲排序完成

複製代碼
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
複製代碼

 

3 直接選擇排序

選擇排序法:每一次從待排序的數據元素中選出最小(或最大)的一個元素,存放到序列的起始位置,直到全部排完。
複製代碼
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)

穩定性:不穩定

 

4 直接插入排序

列表被分爲有序區和無序區兩個部分。最初有序區只有一個元素。
每次從無序區選擇一個元素,插入到有序區的位置,直到無序區變空。
其實就相當於摸牌:

複製代碼
 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)

穩定性:穩定

5 快速排序

取一個元素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)

穩定性:不穩定

特點:就是快

6 堆排序

步驟:
  建立堆
  得到堆頂元素,爲最大元素
  去掉堆頂,將堆最後一個元素放到堆頂,此時可通過一次調整重新使堆有序。
  堆頂元素爲第二大元素。
  重複步驟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),

穩定性:不穩定

特點:通常都比快排慢

7 爲什麼堆排比快排慢?


回顧一下堆排的過程:

1. 建立最大堆(堆頂的元素大於其兩個兒子,兩個兒子又分別大於它們各自下屬的兩個兒子... 以此類推)
2. 將堆頂的元素和最後一個元素對調(相當於將堆頂元素(最大值)拿走,然後將堆底的那個元素補上它的空缺),然後讓那最後一個元素從頂上往下滑到恰當的位置(重新使堆最大化)。
3. 重複第2步。

  這裏的關鍵問題就在於第2步,堆底的元素肯定很小,將它拿到堆頂和原本屬於最大元素的兩個子節點比較,它比它們大的可能性是微乎其微的。實際上它肯定小於其中的一個兒子。而大於另一個兒子的可能性非常小。於是,這一次比較的結果就是概率不均等的,根據前面的分析,概率不均等的比較是不明智的,因爲它並不能保證在糟糕情況下也能將問題的可能性削減到原本的1/2。可以想像一種極端情況,如果a肯定小於b,那麼比較a和b就會什麼信息也得不到——原本剩下多少可能性還是剩下多少可能性。
  在堆排裏面有大量這種近乎無效的比較,因爲被拿到堆頂的那個元素幾乎肯定是很小的,而靠近堆頂的元素又幾乎肯定是很大的將一個很小的數和一個很大的數比較,結果幾乎肯定是“小於”的,這就意味着問題的可能性只被排除掉了很小一部分
  這就是爲什麼堆排比較慢(堆排雖然和快排一樣複雜度都是O(NlogN)但堆排複雜度的常係數更大)。
MacKay也提供了一個修改版的堆排:每次不是將堆底的元素拿到上面去,而是直接比較堆頂(最大)元素的兩個兒子,即選出次大的元素。由於這兩個兒子之間的大小關係是很不確定的,兩者都很大,說不好哪個更大哪個更小,所以這次比較的兩個結果就是概率均等的了

8 歸併排序

 思路:

一次歸併:將現有的列表分爲左右兩段,將兩段裏的元素逐一比較,小的就放入新的列表中。比較結束後,新的列表就是排好序的。

然後遞歸。

複製代碼
 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)

一般情況下,就運行時間而言:
    快速排序 < 歸併排序 < 堆排序

三種排序算法的缺點:
  快速排序:極端情況下排序效率低
  歸併排序:需要額外的內存開銷
  堆排序:在快的排序算法中相對較慢

9 希爾排序

希爾排序是一種分組插入排序算法。
首先取一個整數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)

不是很快,位置尷尬

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