选择问题(求第k个最小元素)
- 什么是選擇問題
- 劃分的思路
- Lomuto 劃分
- 利用劃分求第k小元素
- C語言實現(xiàn)
- 改進
- 參考資料
什么是選擇問題
選擇問題(selection problem)是求一個n個數(shù)列表的第k個最小元素的問題。這個數(shù)字被稱為第k個順序統(tǒng)計量(order statistic)。當然,對于k=1或者k=n的情況,我們可以掃描整個列表,找出最小或者最大的元素。對于其他情況,我們可以對列表進行排序,然后返回第k個元素。
可是,對于整個列表進行排序是不是小題大做?因為該問題僅僅是要找出第k小的元素,而不是要求把列表從小到大排列。
劃分的思路
我們可以將給定的列表根據(jù)某個值p(例如列表的第一個元素)進行劃分。一般來說,這是對列表元素的重新整理,使左邊部分包含所有小于等于p的元素,緊接著是中軸(pivot)p本身,再接著是所有大于等于p的元素。如下圖所示
有兩種主要的劃分方法,本文討論 Lomuto 劃分,以后會介紹更有名的 Hoare 劃分。
Lomuto 劃分
Lomuto(洛穆托)劃分的偽代碼如下:
// 算法:Lomuto_Partition(A[l..r]) // 用第一個元素作為中軸對子數(shù)組進行劃分 // 輸入:數(shù)組A[0..n-1]的一個子數(shù)組A[l..r],它由左右兩邊的索引l和r(l<=r)定義 // 輸出:A[l..r]的劃分和中軸的新位置p = A[l] s = l for i=l+1 to r doif A[i] < ps = s+1;swap(A[s], A[i]) swap(A[l], A[s]) return s利用劃分求第k小元素
我們?nèi)绾卫脛澐至斜韥韺ふ移涞趉小元素呢?
假設列表是以數(shù)組實現(xiàn)的,其元素索引從0開始,那么第k小的元素就是把此列表從小到大排序后,索引在k-1位置上的元素。
假設首次劃分此列表,s是分割位置,也就是劃分后中軸元素的索引。我們分3種情況進行討論:
[1]. 當s=k-1 ,那么中軸p本身顯然就是第k小的元素;
[2]. 如果s>k-1,那么整個列表的第k小元素就是左邊部分的第k小元素;
[3]. 如果s<k-1,那么問題就轉(zhuǎn)換為求右邊部分的第(k-s-1)小元素;推導過程是這樣的:本來是求第k小,通過劃分,篩除了最前面的(s+1)個元素,所以只用求右邊部分(藍色)的第 k-(s+1)小。
可以看出,第2種情況和第3種情況雖然沒有徹底解決問題,但是使問題的實例變小了。對于這個較小的實例可以用同樣的方法來解決,即遞歸求解。這個算法被稱為“快速選擇”,在算法思想中屬于減可變規(guī)模算法(減治法的一種)。
C語言實現(xiàn)
// 交換*a和*b void swap(int* a, int* b) {int temp = *a;*a = *b;*b = temp; } // a是數(shù)組首地址,l和r分別是兩端的索引,要求l<=r int Lomuto_partition(int a[], int l, int r) {int pivot = a[l];int j = l;for(int i = l + 1; i <= r; ++i){if( a[i] < pivot ){swap( &a[++j], &a[i]);}}swap(&a[l], &a[j]);return j; // j是下標 }// 返回第k小的元素值。 // 注意:k不是下標,表示第k個 int __quick_select(int a[], int l, int r, int k) {// s是分裂點,中軸的下標int s = Lomuto_partition(a, l, r);if(s == l+k-1)return a[s];else if( s > (l+k-1) )// 處理左邊的部分return __quick_select(a, l, s-1, k);else // 處理右邊的部分return __quick_select(a, s+1, r, k-(s-l+1)); }// 快速選擇主函數(shù) int quick_select_min(int a[], int len, int k) {return __quick_select(a, 0, len-1, k); }測試代碼如下:
#include <stdio.h> #include <stdlib.h>int main(int argc,char *argv[]) {int k = atoi(argv[1]); //把命令行輸入的字符串轉(zhuǎn)為整數(shù)printf("k = %d \n",k);int array[] = {9,9,8,7,6,5,4,3,3,3,2,2,2,1,1,0,}; int len = sizeof array/sizeof array[0];printf("%dth min = %d\n", k, quick_select_min(array,len,k) );return 0; }運行截圖:
改進
該算法也可以不用遞歸實現(xiàn)。在非遞歸版本中,甚至不需要調(diào)整k的值,只要一直調(diào)用Lomuto_partition,直到s=k-1為止。
代碼如下:
int quick_select_min_2(int a[], int len, int k) {int left = 0;int right = len - 1;int s;// 直到返回的下標是 k-1 為止while( (s = Lomuto_partition(a, left, right)) != (k-1) ){if(s < k-1)left = s+1; //處理右邊的部分elseright = s-1; // 處理左邊的部分}return a[s]; }參考資料
《算法設計與分析基礎(第3版)》(清華大學出版社)
與50位技術(shù)專家面對面20年技術(shù)見證,附贈技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的选择问题(求第k个最小元素)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 外部函数的调用
- 下一篇: 贪心算法与动态规划的区别与联系