常用排序演算法的Python實現
使用python
實現氣泡排序,插入排序,選擇排序,希爾排序,歸併排序,快速排序和堆排序。
氣泡排序
思路:通過交換相鄰的兩個數,使得更小的數在前較大的數在後(或反之),每次便利後,最大的數則被交換至最後。對n
個數需要o(n^2)
的比較次數,對於包含大量元素的數列排序是很沒有效率的。
基本的python
實現:
def BubbleSort(nums): for i in range(len(nums)): for j in range(1, len(nums)-i): if nums[j] < nums[j-1]: nums[j], nums[j-1] = nums[j-1], nums[j] return nums
插入排序
思路:對於未排序資料,在已排序資料中從後向前掃描,找到相應位置並插入。需要用到o(1)
的空間,平均演算法複雜度為o(n^2)
(最好情況下是o(n)
),同樣的也不適合資料量大的排序應用。
def InsertSort(nums): for i in range(len(nums)-1): key = nums[i+1] for j in range(i, -2, -1): if key < nums[j]: nums[j+1] = nums[j] else: break nums[j+1] = key return nums
選擇排序
思路:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然後,再從剩餘未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾。以此類推,直到所有元素均排序完畢(維基百科
)。我寫氣泡排序時,總是會和選擇排序混在一起,與氣泡排序不同的是,每一次交換一對元素,至少有一個將會被移到最終位置上。
def SlectSort(nums): for i in range(len(nums)): min_idx = i for j in range(i+1, len(nums)): if nums[j] < nums[min_idx]: min_idx = j nums[i], nums[min_idx] = nums[min_idx], nums[i] return nums
雖然總共比較次數需要
o(n^2)
,但是交換次數是o(n)
,因此會比氣泡排序要快。
希爾排序
思路:通過將比較的元素分為幾個區域從而提升插入排序的效能,然後逐漸遞減步長,到了最後一步,即是普通的插入排序了。時間複雜度根據陣列長度的變化而變化。
def ShellSort(nums):
step = len(nums) / 2
while step:
for i in range(step):
nums[i::step] = InsertSort(nums[i::step])
step /= 2
return nums
優化過後,不需要每次都重新分配空間:
def ShellSort1(nums):
step = len(nums) / 2
while step:
for i in range(step, len(nums)):
key, j = nums[i], i
# 插入排序
while j-step >= 0 and nums[j-step] > key:
nums[j] = nums[j-step]
j -= step
nums[j] = key
step /= 2
return nums
歸併排序
思路:將陣列分成兩部分,分別對兩部分陣列排序,再進行合併。因此需要兩步:1.遞迴分解陣列,2. 再進行合併。平均/最壞/最優時間複雜度為o(nlogn)
。
def MergeSort(nums):
def helpfunc(nums):
start, end = 0, len(nums)-1
if start < end:
# 選擇mid偏向於end的原因是
# 當start最終等於mid時
# 會使得nums[:mid]永遠為空,nums[mid:]永遠為nums[:],從而進入死迴圈
mid = end - (end - start) / 2
nums1 = helpfunc(nums[:mid])
nums2 = helpfunc(nums[mid:])
return MergeArray(nums1, nums2)
return nums
nums = helpfunc(nums)
return nums
def MergeArray(nums1, nums2):
nums = []
i, j = 0, 0
while i < len(nums1) and j < len(nums2):
if nums1[i] < nums2[j]:
nums.append(nums1[i])
i += 1
else:
nums.append(nums2[j])
j += 1
nums.extend(nums1[i:] or nums2[j:])
return nums
快速排序
思路:每次選取一個基準數,分別從兩端開始遍歷,將比基準數小的值放在前面,比基準數大的值放在後面,遞迴對基準值兩端進行排序。平均/最優時間複雜度為o(nlogn)
,最壞時間為o(n^2)
。
def QuickSort(nums):
def helpfunc(start, end):
if start >= end:
return
left, right = start, end
# 每次選取最左端的值為基準值
key = nums[left]
# left始終放置比key小的值,right始終放置比key大的值
while left < right:
# 從後往前遍歷
while right >= 0 and nums[right] > key:
right -= 1
if left < right:
nums[left] = nums[right]
left += 1
# 從前往後遍歷
while left < right and nums[left] < key:
left += 1
if left < right:
nums[right] = nums[left]
right -= 1
# 直到left=right,找到該key的所在位置
nums[right] = key
helpfunc(start, right-1)
helpfunc(right+1, end)
helpfunc(0, len(nums)-1)
return nums
堆排序
思路:通過構造一個最大堆或者最小堆,每次將最後一個元素與堆頂元素互換,再重新調整為堆,這樣每次取出的元素要麼是最大,要麼就是最小。 初始化建堆的時間複雜度為o(n)
,排序重建堆的時間複雜度為o(nlogn)
,所以總的時間複雜度為o(n+nlogn)=o(nlogn)
。
對於節點
i
來說,其左右節點分別為2*i+1
,2*i+2
,每次調整隻需要從非葉子節點開始。若陣列長度為n
,則堆從第n/2-1
個節點開始往下調整。
def HeapSort(nums):
build_heap(nums)
for i in range(len(nums)-1, -1, -1):
nums[0], nums[i] = nums[i], nums[0]
max_heapify(0, i)
return nums
# 將較大元素下沉
def max_heapify(i, length):
key = nums[i]
left = 2*i+1
while left < length:
# 找出左右兩個節點更小的那個
if left+1 < length and nums[left] > nums[left+1]:
left += 1
# 根節點比子節點都小,無需調整
if key < nums[left]:
break
# 需要注意python的連續賦值
nums[left], nums[i] = nums[i], nums[left]
i = left
left, key = 2*i+1, nums[i]
# 構造堆
def build_heap(nums):
for i in range(len(nums)/2-1, -1, -1):
max_heapify(i, len(nums))
References
: