【算法】快速排序/数组第K小的元素
快速排序
和歸并排序一樣,也是采用分治(Divide and Conquer)思想。分為三步:
分解:將數組A[p...q]劃分成兩個數組A[p..r-1]和A[r+1..q],使得A[p..r-1]中的每個元素都小于等于A[r],并且A[r+1..q]中所有元素大于等于A[r],A[r]稱為主元。
解決:遞歸調用快速排序,對兩個子數組進行排序
合并:不需要合并操作,子數組采用原址排序,已經有序。
和歸并排序相比,歸并排序主要工作在于合并操作,而快速排序在于劃分。劃分數組的過程如下描述
PARTITION(A,p,q)x = A[p]i = p;for j = p+1 to qif A[j] <= xi = i+1exchange A[i] A[j]exchange A[p] A[i]詳細的劃分過程如圖所示
經過一次partition劃分,分成兩個數組A[p..r-1]和A[r+1..q],使得A[p..r-1]中的每個元素都小于等于A[r],并且A[r+1..q]中所有元素大于等于A[r],r為partition的返回值
將數組A進行劃分的關鍵代碼
private int partition(int[] a, int p, int q) {int i = p, j = p + 1;while (j <= q) {if (a[j] <= a[p]) {i++;swap(a, i, j);}j++;}swap(a, i, p);return i;}于是,快速排序的算法描述為
public static void quickSort(int[] a, int p, int q) {if (p < q) {int r = partition(a, p, q);quickSort(a, p, r - 1);quickSort(a, r + 1, q);}}快速排序的性能:
快速排序在再壞情況下,運氣實在糟透了,每次都出現不平等的劃分,將其劃分成了1個元素和n-1個元素,于是有T(n)=T(n-1)+T(1)+θ(n),則:T(n)=O(n^2)
最好情況下,我們假設每次劃分都是平等劃分,即T(n)=2T(n/2)+θ(n),于是T(n)=O(nlgn).
可以證明,只要是常數比例的劃分,算法的運行時間都是T(n)=O(nlgn)。因此,快速排序的平均運行時間更接近于其最好情況而不是最壞情況。
為了保證每次劃分出現常數比例的劃分,在算法中引入隨機性。
private int randomPartition(int[] a, int p, int q) {int i = (int) (Math.random() * (q - p) + p);swap(a, p, i);return partition(a, p, q);}可以證明,快速排序的期望運行時間為T(n)=O(nlgn),最壞運行時間為T(n)=O(n^2)
同時,快速排序也是不穩定的排序算法,例如A=[2,3,1,2],經過第一次劃分后,劃分為[2,1,2,3],這時的r位于下標2處,且與A中的兩個2的順序相比已經交換過一次了。兩個2逆序。
關于找數組第k小的數
其實沒看算法的時候我覺得需要排序,然后可以在O(1)的時間里面找到第k小的數,學習了算法就不能犯這樣的錯誤啦,常用的好的排序算法比如快速排序,它的時間復雜度為O(nlogn)。事實上我們可以在O(n)的時間內找到數組的第k小的元素。
答案就在上面講的快速排序中,事實上,在上面的快速排序的劃分過程中其實已經產生了第K小的數。
可以參考下圖所示
獲得數組第k小的數的關鍵代碼
public static int getKthValue(int[] a, int p, int q, int k) {if (p == q) return a[p];int r = randomPartition(a, p, q);int i = r - p + 1;// r是當前序列里面第i小的數字if (i == k) {return a[r];} else if (k < i) {return getKthValue(a,p,r-1,k);} else return getKthValue(a,r+1,q,k - i);}這種算法的時間復雜度可以達到期望為線性。E[T(n)]=O(n)轉載于:https://www.cnblogs.com/qhyuan1992/p/5385285.html
總結
以上是生活随笔為你收集整理的【算法】快速排序/数组第K小的元素的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: javascript 动态创建tip图片
- 下一篇: PHP - 会话控制