算法题04:分治法:求第K小元素(线性时间选择算法)
題目內容:
給定一個線性序列集,要求使用分治法求出其中指定的第 KKK 小的數的值和位置,如給定 nnn 個元素和一個整數 iii,1≤i≤n1≤i≤n1≤i≤n,輸出這 nnn 個元素中第 iii 小元素的值及其位置。
一、問題分析(模型、算法設計和正確性證明等)
解決第K小問題有如下幾種方法
①將n個數排序(比如快速排序或歸并排序),選取排序后的第k個數,時間復雜度為O(nlogn)O(nlogn)O(nlogn)。
②維護一個k個元素的最大堆,存儲當前遇到的最小的k個數,時間復雜度為O(nlogk)O(nlogk)O(nlogk)。這種方法同樣適用于海量數據的處理。
③部分的快速排序(快速選擇算法),每次劃分之后判斷第k個數在左右哪個部分,然后遞歸對應的部分,平均時間復雜度為O(n)O(n)O(n)。但最壞情況下復雜度為O(n2)O(n^2)O(n2)。
④線性時間選擇算法,修改快速選擇算法的主元選取規則,使用中位數的中位數的作為主元,最壞情況下時間復雜度為O(n)O(n)O(n)。
? 實驗要求使用分治策略,所以可以使用歸并排序、快速排序、以及線性時間選擇算法,我選擇的是線性時間選擇算法,本質上是快速排序的升級版,算法的思想是修改快速選擇算法的主元選取方法,提高算法在最壞情況下的時間復雜度。
? 算法的思路是:
二、復雜度分析
? 當n<75n<75n<75時,時間復雜度很低,近似于常數, n大于75時,劃分時以5個元素為一組求取中位數,共得到n/5個中位數,再遞歸求取中位數,復雜度為T(n/5),剩余要處理的最大規模變為3n/43n/43n/4,加上多次線性掃描,其時間復雜度為C2nC_{2}nC2?n,所以可以得到遞推式:
T(n)≤{C1n<75C2n+T(n/5)+T(3n/4)n≥75∴T(n)=O(n)\begin{aligned} T(n) & \leq\left\{\begin{array}{ll}C_{1} & n<75 \\ C_{2} n+T(n / 5)+T(3 n / 4) & n \geq 75\end{array}\right.\\ \therefore T(n)=O(n) \end{aligned}T(n)∴T(n)=O(n)?≤{C1?C2?n+T(n/5)+T(3n/4)?n<75n≥75??
三、程序實現和測試過程和結果(主要描述出現的問題和解決方法)
偽代碼:
public static int DAC_Linear_Select(int a[], int l, int r, int key) {if(a.length <= 5): //當長度小于5時,直接使用冒泡排序,返回查找到的值bubbleSort(a,l,r);return a[key-1];int index=l-1; //定義中位數替換下標for(int star=l,end; (end=star+4)<=r; st+=5){bubbleSort(a,star,end);//每五個數排序swap(a,++t,star+2); //將中位數替換到數組前面,便于遞歸求取中位數的中位數}int pivot=DAC_Linear_Select(a, l, l+(r-l-4)/5, (l+(r+l-4)/5-l+1)/2);int i = partition(a, l, r, pivotId), j = i-l+1;if(key==j) return a[j]; //相等則直接返回else if(key<j) return DAC_Linear_Select(a,l,j-1,key); //小于則向左遞歸else return DAC_Linear_Select(a, j+1,r,key-j) //大于則向右遞歸 }java代碼:
package root;/*** @author 宇智波Akali* 這是線性時間選擇算法(BFPRT算法)解決前k小問題程序* @version2.0*/ public class T3_DAC_Liner_SelectN {public static void swap(int a[], int i,int j){int temp=a[j];a[j] = a[i];a[i] = temp;}//冒泡排序public static void bubbleSort(int a[], int l, int r){for(int i=l; i<r; i++){for(int j=i+1; j<=r; j++){if(a[j]<a[i])swap(a,i,j);}}}//遞歸尋找中位數的中位數public static int FindMid(int a[], int l, int r){if(l == r) return l;int i = 0;int n = 0;for(i = l; i < r - 5; i += 5){bubbleSort(a, i, i + 4);n = i - l;swap(a,l+n/5, i+2);}//處理剩余元素int num = r - i + 1;if(num > 0){bubbleSort(a, i, i + num - 1);n = i - l;swap(a,l+n/5, i+num/2);}n /= 5;if(n == l) return l;return FindMid(a, l, l + n);}//進行劃分過程public static int Partion(int a[], int l, int r, int p){swap(a,p, l);int i = l;int j = r;int pivot = a[l];while(i < j){while(a[j] >= pivot && i < j)j--;a[i] = a[j];while(a[i] <= pivot && i < j)i++;a[j] = a[i];}a[i] = pivot;return i;}public static int Select(int a[], int l, int r, int k){int p = FindMid(a, l, r); //尋找中位數的中位數int i = Partion(a, l, r, p);int m = i - l + 1;if(m == k) return a[i];if(m > k) return Select(a, l, i - 1, k);return Select(a, i + 1, r, k - m);}public static void main(String[] args) {int a[]= {3,0,7,6,5,9,8,2,1,4,13,11,17,16,15,19,18,12,10,14,23,21,27,26,25,29,28,22,20,24,33,31,37,36,35,39,38,32,30,34,43,41,47,46,45,49,48,42,40,44,53,51,57,56,55,59,58,52,50,54,63,61,67,66,65,69,68,62,60,64,73,71,77,76,75,79,78,72,70,74};for(int i = 0; i < 80; i++){System.out.println("第"+(i+1)+"小數為: "+Select(a, 0, 79, i+1));}} }? 算法的思路倒是容易理解,但是把算法轉換為代碼就有太多細節要處理,不同編程語言之間也有差異,一開始用Java寫的,結果因為參數傳遞的機制沒注意,數組值交換有bug,java中定義swap(a[],i,j),要把數組也傳進去。
運行結果:
我輸入的數組為10個隨機數,循環輸出低k小數,結果如下:
為了確保正確性,我又測試了長度為80的0-79的亂序數組,結果也正確。
總結
以上是生活随笔為你收集整理的算法题04:分治法:求第K小元素(线性时间选择算法)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 前端学习(2985):一文理解数据劫持3
- 下一篇: 前端学习(2989):vue+eleme