排序算法的穩定性
排序算法(英語:Sorting algorithm)是一種能將一串數據依照特定順序進行排列的一種算法。
穩定性:穩定排序算法會讓原本有相等鍵值的紀錄維持相對次序。也就是如果一個排序算法是穩定的,當有兩個相等鍵值的紀錄R和S,且在原本的列表中R出現在S之前,在排序過的列表中R也將會是在S之前。
當相等的元素是無法分辨的,比如像是整數,穩定性並不是一個問題。然而,假設以下的數對將要以他們的第一個數字來排序。
(4, 1) (3, 7) (3, 1) (5, 6)
在這個狀況下,有可能產生兩種不同的結果,一個是讓相等鍵值的紀錄維持相對的次序,而另外一個則沒有:
(3, 7) (3, 1) (4, 1) (5, 6) (維持次序)
(3, 1) (3, 7) (4, 1) (5, 6) (次序被改變)
不穩定排序算法可能會在相等的鍵值中改變紀錄的相對次序,但是穩定排序算法從來不會如此。不穩定排序算法可以被特別地實現為穩定。作這件事情的一個方式是人工擴充鍵值的比較,如此在其他方面相同鍵值的兩個對象間之比較,(比如上面的比較中加入第二個標准:第二個鍵值的大小)就會被決定使用在原先數據次序中的條目,當作一個同分決賽。然而,要記住這種次序通常牽涉到額外的空間負擔。
排序算法的穩定性即是,如果只按照第一個數字排序的話,第一個數字相同而第二個數字不同的,第二個數字按照原有排序的就是穩定排序,不按照原有排序的就是不穩定排序。
冒泡排序及實現
單行比較一對相鄰兩個數字之間更大的數字,將更大的數字放在右邊;再換下一行,將前一行大的那個數字再與下一個數字比較放在右邊,所有行走一遍就可以排出一個最大的數字來放在最右邊;排除一個數字后再所有行走一遍排除第二大的數字放在最大的數字的左邊,一次類推,n個數經過 n-1 次可以完成從小到大排序。
冒泡排序是穩定性排序。
i = n-2 j = 1 i = n-3 j = 2 i+j = n-1 i = n-1-j
冒泡排序的實現

def bubble_sort(alist): n = len(alist) # 外層for循環,走n-1次將所有的元素進行排序 # range(n-1) 表示的是 0, n-2,其中有n-2+1 for j in range(n - 1): # 內存for循環,走一次就將一個最大的數從前往后排 # 假如有10個數,只要走到第9個數就可以了,9與9+1進行比較排序;但因為我們使用的是下標,所以n-1的下標為n-2,為range(0, n-1) count = 0 for i in range(0, n-1-j): if alist[i] > alist[i + 1]: alist[i], alist[i + 1] = alist[i + 1], alist[i] if count == 0: return def main(): li = [22, 11, 55, 44, 100, 99, 80] print(li) bubble_sort(li) print(li) if __name__ == '__main__': main()
選擇排序算法及實現
選擇排序算法可以理解為,將列表分為兩部分,剛開始時左邊一組為空,右邊為所有元素;然后從右邊選出一個最小的數字,放在最左邊,然后左右兩部分就是,左邊一個數字,右邊是其余的數字;然后再從右邊選出一個最小的數字,放在左邊第二位,以此類推,就可以完成排序了。始終從后面未排序的數組中選出一個最小的數字放在左邊。
從右邊選出一個最小的數字放在左邊的過程可以理解為,初始時定義最小的min為下標為0的索引的數字,然后與0后面的索引數字進行比較,如果還有數字比索引0數字更小的就將min指向后面那個更小數字的索引,最后走了一遍后確定最小數字的索引min,然后將alist[0], alist[min] = alist[min], alist[0],這樣就排好了第一個數字。然后再定義min為下標為1的數字,再和右邊的進行比較,如果右邊還有比索引1數字更小的數字,就將min指向后面更小數字的索引,最后確定最小數字索引,交換索引1數字和最小數字索引的位置,alist[1], alist[min] = alist[min], alist[i]。以此類推完成排序。
選擇排序方法的最壞時間復雜度為 O(n^2),因為其算法問題,沒有最優時間復雜度,也沒有優化方法;
這里采用的是升序選擇最小的方法進行排列,是穩定性排序;但如果你采用的是升序選擇最大的方法,那么就是不穩定排序了;所以總體來說,我們認為選擇排序是不穩定的。
選擇排序的實現

def select_sort(alist): """選擇排序""" n = len(alist) for j in range(0, n - 1): min_index = j # 內層循環,一次大循環從右邊部分挑出一個最小數,將最小數依次放在左邊 for i in range(j+1, n): if alist[min_index] > alist[i]: min_index = i alist[j], alist[min_index] = alist[min_index], alist[j] def main(): li = [22, 11, 55, 44, 100, 99, 80] print(li) select_sort(li) print(li) if __name__ == '__main__': main()
插入排序
插入排序(英語:Insertion Sort)是一種簡單直觀的排序算法。它的工作原理是通過構建有序序列,對於未排序數據,在已排序序列中從后向前掃描,找到相應位置並插入。插入排序在實現上,在從后向前掃描過程中,需要反復把已排序元素逐步向后挪位,為最新元素提供插入空間。
插入算法可以理解為,還是將一個列表分為兩部分,當第一次時,將列表第一個數字當做左邊部分,其余做右邊部分;第二次時,將右邊第一個數字與左邊的數字進行比較,如果更大就插入到左邊第二位,更小就插入到左邊第一位;第三次時,將右邊第一個數字與左邊部分從大到小比較,即首先與左邊最右邊的最大的數字比較,如果更大則插入到左邊部分第三位,更小則插入到左邊最大數的前一位,再與左邊部分第二大的數字進行比較,如果更大則說明這個數字是左邊部分第二大的數字,則不動,如果更小就插入到左邊部分第二大數字的左邊;以此類推即可完成排序;
插入排序就是依次從右邊取出第一個數字,並將數字插入道左邊部分合適的位置。
[22, 11, 55, 13, 44, 100, 99, 80] [11, 22, 55, 13, 44, 100, 99, 80] [11, 22, 55, 13, 44, 100, 99, 80] [11, 22, 13, 55, 44, 100, 99, 80] [11, 13, 22, 55, 44, 100, 99, 80] [11, 13, 22, 44, 55, 100, 99, 80] [11, 13, 22, 44, 55, 100, 99, 80] [11, 13, 22, 44, 55, 99, 100, 80] [11, 13, 22, 44, 55, 99, 80, 100, ] [11, 13, 22, 44, 55, 80, 99, 100, ]
插入排序的實現

def insert_sort(alist): """插入排序""" n = len(alist) # 外層循環,決定從右邊的無序列表中去除多少個元素執行這樣的過程 for j in range(1, n): # i = [1, 2, 3, ... , n-1] i = j # 內層循環,循環一次,就將右邊的一個數字插入到左邊部分正確的位置中 while i > 0: if alist[i] < alist[i - 1]: alist[i - 1], alist[i] = alist[i], alist[i - 1] i -= 1 else: break def main(): li = [22, 11, 55, 13, 44, 100, 99, 80] print(li) insert_sort(li) print(li) if __name__ == '__main__': main() # 運行結果: # [22, 11, 55, 13, 44, 100, 99, 80] # [11, 13, 22, 44, 55, 80, 99, 100]
時間復雜度
最優時間復雜度:O(n) (升序排列,序列已經處於升序狀態)
最壞時間復雜度:O(n2)
穩定性:穩定
希爾排序
希爾排序過程
希爾排序的基本思想是:將數組列在一個表中並對列分別進行插入排序,重復這過程,不過每次用更長的列(步長更長了,列數更少了)來進行。最后整個表就只有一列了。將數組轉換至表是為了更好地理解這算法,算法本身還是使用數組進行排序。
例如,假設有這樣一組數[ 13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10 ],如果我們以步長為5開始進行排序,我們可以通過將這列表放在有5列的表中來更好地描述算法,這樣他們就應該看起來是這樣(豎着的元素是步長組成):
13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10
然后我們對每列進行排序:
10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45
將上述四行數字,依序接在一起時我們得到:[ 10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45 ]。這時10已經移至正確位置了,然后再以3為步長進行排序:
10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45
排序之后變為:
10 14 13 25 23 33 27 25 59 39 65 73 45 94 82 94
最后以1步長進行排序(此時就是簡單的插入排序了)
希爾排序的實現

def shell_sort(alist): n = len(alist) # 初始步長 gap = n // 2 while gap > 0: # 按步長進行插入排序 for i in range(gap, n): j = i # 插入排序 while j >= gap and alist[j - gap] > alist[j]: alist[j - gap], alist[j] = alist[j], alist[j - gap] j -= gap # 得到新的步長 gap = gap // 2 li = [22, 11, 55, 13, 44, 100, 99, 80] print(li) shell_sort(li) print(li)
時間復雜度
最優時間復雜度:根據步長序列的不同而不同
最壞時間復雜度:O(n2)
穩定想:不穩定
快速排序
快速排序的實現

def quick_sort(alist, start, end): """快速排序""" # 遞歸的退出條件 if start >= end: return # 設定起始元素為要尋找位置的基准元素 mid = alist[start] # low為序列左邊的由左向右移動的游標 low = start # high為序列右邊的由右向左移動的游標 high = end while low < high: # 如果low與high未重合,high指向的元素不比基准元素小,則high向左移動 while low < high and alist[high] >= mid: high -= 1 # 將high指向的元素放到low的位置上 alist[low] = alist[high] # 如果low與high未重合,low指向的元素比基准元素小,則low向右移動 while low < high and alist[low] < mid: low += 1 # 將low指向的元素放到high的位置上 alist[high] = alist[low] # 退出循環后,low與high重合,此時所指位置為基准元素的正確位置 # 將基准元素放到該位置 alist[low] = mid # 對基准元素左邊的子序列進行快速排序 quick_sort(alist, start, low - 1) # 對基准元素右邊的子序列進行快速排序 quick_sort(alist, low + 1, end) li = [54, 26, 93, 17, 77, 31, 44, 55, 20] print(li) quick_sort(li, 0, len(li) - 1) print(li)
時間復雜度
最優時間復雜度:O(nlogn)
最壞時間復雜度:O(n2)
穩定性:不穩定
歸並排序
歸並排序的實現

def merge_sort(alist): """歸並排序""" n = len(alist) if n <= 1: return alist mid = n//2 # left 采用歸並排序后形成的有序的新的列表 left_li = merge_sort(alist[:mid]) # right 采用歸並排序后形成的有序的新的列表 right_li = merge_sort(alist[mid:]) # 將兩個有序的子序列合並為一個新的整體 # merge(left, right) left_pointer, right_pointer = 0, 0 result = [] while left_pointer < len(left_li) and right_pointer < len(right_li): if left_li[left_pointer] <= right_li[right_pointer]: result.append(left_li[left_pointer]) left_pointer += 1 else: result.append(right_li[right_pointer]) right_pointer += 1 result += left_li[left_pointer:] result += right_li[right_pointer:] return result if __name__ == "__main__": li = [54, 26, 93, 17, 77, 31, 44, 55, 20] print(li) sorted_li = merge_sort(li) print(li) print(sorted_li)
時間復雜度
最優時間復雜度:O(nlogn)
最壞時間復雜度:O(nlogn)
穩定性:穩定
二分查找
二分查找條件:
- 1.查找的數據必須是經過排序的;
- 2.數據必須是相鄰的;
- 3.必須可以索引;
所以二分查找只能作用在有序的順序表中;
二分查找的實現

def binary_search(alist, item): """二分查找,遞歸""" n = len(alist) if n > 0: mid = n // 2 if alist[mid] == item: return True elif item < alist[mid]: return binary_search(alist[:mid], item) else: return binary_search(alist[mid + 1:], item) return False def binary_search_2(alist, item): """二分查找, 非遞歸""" n = len(alist) first = 0 last = n - 1 while first <= last: mid = (first + last) // 2 if alist[mid] == item: return True elif item < alist[mid]: last = mid - 1 else: first = mid + 1 return False if __name__ == "__main__": li = [17, 20, 26, 31, 44, 54, 55, 77, 93] print(binary_search(li, 55)) print(binary_search(li, 100)) print(binary_search_2(li, 55)) print(binary_search_2(li, 100))
時間復雜度
最優時間復雜度:O(1)
最壞時間復雜度:O(logn)