排序演算法: 三大中級排序演算法,原理解析及用法
阿新 • • 發佈:2018-12-04
三大中級演算法
- 難度 ★★
- 演算法複雜度O(nlogn)
- 一般情況下排序時間: 快速排序< 歸併排序 < 堆排序
快速排序
: 缺點極端情況下效率低堆排序
: 缺點在快的排序演算法中相對慢歸併排序
: 缺點要有額外記憶體空間
快速排序 ★★
quick Sort
演算法複雜度: O(nlogn) <n乘logn>
思路
每趟取第一個元素,讓列表被該元素分為兩部分,左邊的比他小,右邊比他大, 重複用每次得來的第一個元素遞迴
問題1:
遞迴py有最大深度問題999次!(雖然可以設定)
問題2:
最壞的情況,如果列表本身是一個倒序列表,那麼效率相對較低
(解決方案:隨機快排,一開始就隨機取一個數放在最左邊開始排)
def quick_sort(li, left, right):
"""
快速排序
"""
if left < right: # 至少兩個元素
mid = partition(li, left, right)
quick_sort(li, left, mid-1)
quick_sort(li, mid+1, right)
def partition (li, left, right):
"""
將下標為0的元素為參照物,左邊的放比他小的,右邊放比他大的
"""
tmp = li[left] # 第一個位置
while left < right: # 一直迴圈
while left < right and li[right] >= tmp: # 右邊開始找比tmp 小的數放到左邊的空位
right -= 1 # 往左走一步
li[left] = li[right] # 把右邊的值寫到左邊空位
# 有可能所有數都比自己大
while left < right and li[left] <= tmp:
left += 1
li[right] = li[left] # 把左邊的值寫到右邊的空位上
li[left] = tmp # 把tmp歸位
return left # 或者返回right都行
li_test = [3, 2, 7, 1, 6, 9, 8, 4]
quick_sort(li_test, 0, len(li_test)-1)
print(li_test)
堆排序 ★★
演算法複雜度: O(nlogn) <n乘logn>
前提
二叉樹的知識儲備!!
效率:
快排的時間複雜度優於堆排
def sift(li, low, high):
"""
調整堆
:param li: 列表
:param low: 堆的根節點
:param high: 堆的最後一個元素位置
:return:
"""
i = low # i最開始指向的父
j = 2 * i + 1 # 堆頂左孩子
tmp = li[low] # 把堆頂存起來
while j <= high: # 只要j位置有數
if j + 1 <= high and li[j + 1] > li[j]: # 如果右還在比較大且右孩子有
j = j + 1 # j指向右孩子
if li[j] > tmp:
li[i] = li[j]
i = j # 往下一步看
j = 2 * i + 1
else: # tmp更大,把tmp放到i位置上
li[i] = tmp # 把tmp放到某一級領導位置上
break
else:
li[i] = tmp # 把tmp放到葉子節點上
def heap_sort(li):
"""
堆排序
"""
n = len(li)
for i in range((n-2)//2, -1, -1):
# i表示建堆時調整的部分的根下標
sift(li, i, n-1)
# 建堆完成了
for i in range(n-1, -1, -1):
# i向當前堆最後一個元素
li[0], li[i] = li[i], li[0]
sift(li, 0, i-1) # i-1是新的high
li_test = [3, 2, 7, 1, 6, 9, 8, 4]
heap_sort(li_test)
print(li_test)
堆排序py 模組
import heapq # q : queue 優先列隊
import random
li = list(range(100))
random.shuffle(li) # 打亂
print(li)
heapq.heapify(li) # 建堆
n = len(li)
for i in range(n):
print(heapq.heappop(li), end=',')
堆排序 topk問題 【常用】
問題:比如熱搜網,有n個數,取前k打的數(排序好的)
思路
- 取列表前k個元素建立小根堆,堆頂是目前第k大的數
- 依次向後面遍歷原列表,對於列表中的元素,如果小於堆頂,則忽略該元素,反之換為該元素,並進行一次調整
- 遍歷素有元素後,倒序彈出堆頂
import random
def sift(li, low, high):
"""
調整為小根堆
:param li: 列表
:param low: 堆的根節點
:param high: 堆的最後一個元素位置
:return:
"""
i = low # i最開始指向的父
j = 2 * i + 1 # 堆頂左孩子
tmp = li[low] # 把堆頂存起來
while j <= high: # 只要j位置有數
if j + 1 <= high and li[j + 1] < li[j]: # 如果右還在比較大且右孩子有
j = j + 1 # j指向右孩子
if li[j] < tmp:
li[i] = li[j]
i = j # 往下一步看
j = 2 * i + 1
else: # tmp更大,把tmp放到i位置上
li[i] = tmp # 把tmp放到某一級領導位置上
break
else:
li[i] = tmp # 把tmp放到葉子節點上
def top_key(li, k):
heap = li[0:k]
for i in range((k-2)//2, -1, -1):
sift(heap, i, k-1)
# 1.建堆
for i in range(k, len(li)-1):
if li[i] > heap[0]:
heap[0] = li[i]
sift(heap, 0, k-1)
# 2.遍歷
for i in range(k-1, -1, -1):
heap[0], heap[i] = heap[i], heap[0]
sift(heap, 0, i-1)
# 3.出數
return heap
li = list(range(1000))
random.shuffle(li)
print(top_key(li, 10)) # 測試 前10數
歸併排序 ★★
演算法複雜度: O(nlogn) <n乘logn>
空間複雜度: O(n)
思路
假設兩個列表已有序,那麼將他們合併在一起,將列表越分越小,直到分為一個元素
def merge(li, low, mid, high):
"""
:param li: 列表
:param low: 左列表第一個元素
:param mid: 左列表最後一個元素,那麼右列表第一個就是mid+1
:param high: 右列表最後一個元素
:return:
"""
i = low # 第一段第一個元素
j = mid + 1 # 第二段的第一個元素
tmp_list = []
while i <= mid and j <= high: # 必須左右有數
if li[i] < li[j]:
tmp_list.append(li[i])
# 移動後箭頭必須移動一位,因為已經把小的值提出到臨時list中
i += 1
else:
tmp_list.append(li[j])
j += 1
# while 執行完,說明有一段執行完了,剩下的接到tmp中即可
# 分別判斷一下那個還有數
while i <= mid:
tmp_list.append(li[i])
i += 1
while j <= high:
tmp_list.append(li[j])
j += 1
li[low:high+1] = tmp_list # 寫回去
def merge_sort(li, low, high):
if low < high: # 至少有2個元素,遞迴
mid = (low + high) // 2 # 整除2
merge_sort(li, low, mid)
merge_sort(li, mid+1, high)
merge(li, low, mid, high)
li_test = [3, 2, 7, 1, 6, 9, 8, 4]
merge_sort(li_test, 0, len(li_test)-1)
print(li_test)