這種算法利用了快速排序的主要思想:每次調用快速排序的partition函數後將會得出一個數n的最終位置,比較n及其右邊所有數的總個數與目標個數k
若大於k,則對數n右邊的數構成的數組調用partition函數; 若等於k,則說明n及其右邊的數就是我們想要得到的那k個數,排序結束; 若小於k,這說明最終的k個數中還包含了n左邊的數,對n左邊的數構成的數組調用partition函數。
Pyhton代碼:
import numpy as np def qselect(A, k): if len(A) < k: return A pivot = A[-1] tmp = A[:-1][A[:-1] >= [pivot]] right = np.insert(tmp, 0, pivot) try: rlen = len(right) except: rlen = 1 if rlen == k: return right if rlen > k: return qselect(right, k) else: left = [x for x in A[:-1] if x < pivot] return np.append(qselect(left, k-rlen), right) L = np.random.randint(0, 10000, size=10000) k = 100 qselect(L, k)
利用快速排序解決topk問題時,速度非常快,但是缺點它在數組中直接交換數據,所以要用一個數組來存儲所有的數據,當數據量十分龐大時將佔用驚人的內存,屬於用空間換時間的算法。
這種算法的大致思路爲:首先初始化一個大小爲k的小根堆,然後遍歷數組,每遍歷一個數就將其與堆的根節點相比較,若比根節點大,則將其與根節點交換變更新堆結構。當數組遍歷完成時堆中存儲的就是我們想要的結果。
Pyhton代碼:
import numpy as np def sift(l, low, hight): # 調整堆結構 tmp = l[low] i = low j = 2 * i + 1 while j <= hight: # 情況2:i已經是最後一層 if j + 1 <= hight and l[j + 1] < l[j]: # 右孩子存在並且小於左孩子 j += 1 if tmp > l[j]: l[i] = l[j] i = j j = 2 * i + 1 else: break # 情況1:j位置比tmp小 l[i] = tmp def top_k(l, k): heap = l[:k] # 建堆 for i in range(k // 2 - 1, -1, -1): sift(heap, i, k - 1) for i in range(k, len(l)): if l[i] > heap[0]: heap[0] = l[i] sift(heap, 0, k - 1) for i in range(k - 1, -1, -1): heap[0], heap[i] = heap[i], heap[0] sift(heap, 0, i - 1) return heap L = np.random.randint(0, 10000, size=10000) k = 100 top_k(L, k)
利用堆排序解決topk問題時,速度是所有算法中除了快速排序之外最快的,它的突出優點就是不論數據量有多大,它的空間複雜度總是常數級的,在處理大數據時常將數據分塊打包後分別利用堆排序進行篩選,再將篩選結果進行比較,選出我們想要的最終結果。
縱座標爲耗時,橫座標爲初始序列長度,數值取值範圍爲[0,999999999],k(即最終篩選出的數值個數)爲100,