線性時間選擇算法
最近算法課的知識點之一,自己以前沒遇見過(是我菜了沒錯),這次寫一篇。
那麼什麼是線性時間選擇呢???一句話,在線性時間內完成選擇。
一般情況下是這樣的,我們想要找出一個數組中的最大值或最小值,那就只需要一次排列,然後輸出第一個或最後一個元素就行了,但如果是要找出一個數組中的第k小的元素呢?
-
在某些特殊情況下,很容易設計出解選擇問題的線性時間算法。如:當要選擇最大元素或最小元素時,顯然可以在O(n)時間完成。(遍歷一次)
-
一般的選擇問題,特別是中位數的選擇問題似乎比最小(大)元素要難。但實際上,從漸近階的意義上,它們是一樣的。也可以在O(n)時間完成。
之前我記得我更過一篇,裏面說到就是先排個序,然後通過下標去得到,因此這個問題的解決效率取決於排序算法的效率。
但是現在不一樣了,我們有了更好的辦法。
隨機劃分線性選擇
分析
隨機劃分線性選擇。模仿快速排序算法,對數組在left——right範圍內進行一次劃分(partition方法,我們這裏默認採用隨機元素爲基準),得到基準下標i,不大於基準的在左邊,比基準大的在右邊。計算出left——i(包含i)中有多少個元素(j),與k進行比較。
- 如果k<j,說明當前left——right中第k小的在左邊序列中,那麼從left——i-1繼續上述步驟尋找第k小。
- 如果k=j,說明基準i就是left——right中第k小,返回a[j]。(因爲前面的都不大於它,後面的都大於它,不管左邊是序列怎麼排的,整體第k(j)小就是他)
- 如果k>j,說明第k小在右邊序列,大於基準a[i],那麼從i+1——right繼續劃分,並將k改爲k-j。(整體序列提出了前j個元素,那麼第k小在剩下的序列中就是第k-j小了)
一直到left=right的時候就可以返回a[left]了(其實我覺得如果有上述2的情況的話就不需要這個了)
這是課堂上PPT給的思路
這是算法導論的(就是我剛剛說的)
代碼
/*
* @Title randomizedSelect
* @Description 基於隨機元素爲基準的線性時間選擇
* @author 滑技工廠
* @Date 2020/3/26
* @param [a, L, R, k -> L---R中的第k小]
* @return int
* @throws
*/
public static int randomizedSelect(int[] a, int L, int R, int k) {
if (L == R)
return a[L];
//獲取爲基準的隨機元素的下標
int i = randomizedPartition(a, L, R);
//j爲劃分後左序列到基準(包含基準)的元素個數
int j = i - L + 1;
if (k < j)//如果k小於j,說明在基準i的左邊
return randomizedSelect(a, L, i - 1, k);
else if (k == j)//
return a[i];
else//k大於j 說明在i的右邊序列
return randomizedSelect(a, i + 1, R, k - j);
}
利用中位數線性時間選擇
分析
算法的思路:如果能在線性時間內找到一個劃分基準使得按這個基準所劃分出的2個子數組的長度都至少爲原數組長度的ε倍(0<ε<1),那麼就可以在最壞情況下用O(n)時間完成選擇任務。例如,當ε=9/10,算法遞歸調用所產生的子數組的長度至少縮短1/10。所以,在最壞情況下,算法所需的計算時間T(n)滿足遞推式T(n)<=T(9n/10)+O(n)。由此可得T(n)=O(n)。
先描述下過程。
將n個輸入元素劃分成n/5個組,每組5個元素,最只可能有一個組不是5個元素。用任意一種排序算法,將每組中元素排好序,並取出中位數,共n/5個。
遞歸調用Select來找出這n/5個元素中的中位數。如果n/5是個偶數,就找它兩個中位數中較大的一個。以該元素作爲劃分基準。
這種情況下,找出的基準x至少比3(n-5)/10個元素大,同理也比3(n-5)/10個元素小。(下圖中箭頭指向是從大到小,紅+藍的數量爲3(n-5)/10)而當n>=75時,3(n-5)/10>=n/4所以按此基準劃分所得的兩個子數組的長度都至少縮短1/4。
得到劃分基準後,後面就和第一個一樣,計算左序列個數,和k進行比較。判斷在左序列還是右序列,在遞歸調用該方法(k在右序列仍要減j),直到k=j爲止,返回那個基準。
例題
代碼
/*
* @Title select
* @Description 利用中位數線性時間選擇
* @author 滑技工廠
* @Date 2020/3/27
* @param [a, l, r, k]
* @return int
* @throws
*/
public static int select(int[] a, int l, int r, int k) {
if (r - l < 75) {
insertSort(a, l, r); //用插入排序進行排序
return a[l + k - 1];
}
int group = (r - l + 5) / 5;
for (int i = 0; i < group; i++) {
int left = l + 5 * i;
int right = (l + i * 5 + 4) > r ? r : l + i * 5 + 4; //如果超出右邊界就用右邊界賦值
int mid = (left + right) / 2;
insertSort(a, left, right);
swap(a, l + i, mid); // 將各組中位數與前i個
}
int pivot = select(a, l, l + group - 1, (group + 1) / 2); //找出中位數的中位數
int p = partition(a, l, r, pivot); //用中位數的中位數作爲基準的位置
int j = p - l + 1; //leftNum用來記錄基準位置的前邊的元素個數
if (k == j)
return a[p];
else if (k < j)
return select(a, l, p - 1, k);
else //若k在基準位子的後邊,則要從基準位置的後邊數起,即第(k - leftNum - 1)個
return select(a, p + 1, r, k - j - 1);
}
詳細代碼去我的G站
作業又完成了一個 (^-^)V