一、題目
對一個含有n個元素的集合來說,所謂k分位元(the kth quantile),就是能把已排序的集合分成k個大小相等的集合的k-1個順序統計量。給出一個能列出某一集合的k分位元的O(nlgk)時間的演算法
二、思考
令每個子集合的元素個數為t = n / k,A[j]是數組A中下標為j的元素,A(j)是數組是第j大的元素
則所求的k分位元是指A(t),A(2t),A(3t),……,A((k-1)t)
按順序依次求這k-1個數的運行時(k-1)*n
要使已耗用時間為O(nlgk),改進方法是不要依次尋找這k-1個數,而是借用二分的方法來找。
先找第k/2個分位元,再以這個分位元為主元把數組分為兩段,分別對這兩段來找分位元,這個時候找的範圍變小了,效率也就提高了
三、代碼
#include <iostream>using namespace std;int t, length_A;void Print(int *A, int len){int i;for(i = 1; i <= len; i++)cout<<A[i]<<' ';cout<<endl;}/*************最壞情況線性時間的選擇**************************************************///已經出現很多次了,不解釋int Partition(int *A, int p, int r){int x = A[r], i = p-1, j;for(j = p; j < r; j++){if(A[j] <= x){i++;swap(A[i], A[j]);}}swap(A[i+1], A[r]);return i+1;}int Select(int *A, int p, int r, int i);//對每一組從start到end進行插入排序,並返回中值//插入排序很簡單,不解釋int Insert(int *A, int start, int end, int k){int i, j;for(i = 2; i <= end; i++){int t = A[i];for(j = i; j >= start; j--){if(j == start)A[j] = t;else if(A[j-1] > t)A[j] = A[j-1];else{A[j] = t;break;}}}return A[start+k-1];}//根據文中的演算法,找到中值的中值int Find(int *A, int p, int r){int i, j = 0;int start, end, len = r - p + 1;int *B = new int[len/5+1];//每5個元素一組,長度為start到end,對每一組進行插入排序,並返回中值for(i = 1; i <= len; i++){if(i % 5 == 1)start = i+p-1;if(i % 5 == 0 || i == len){j++;end = i+p-1;//對每一組從start到end進行插入排序,並返回中值,如果是最後一組,組中元素個數可能少於5int ret = Insert(A, start, end, (end-start)/2+1);//把每一組的中值挑出來形成一個新的數組B[j] = ret;}}//對這個數組以遞迴調用Select()的方式尋找中值int ret = Select(B, 1, j, (j+1)/2);//delete []B;return ret;}//以f為主元的劃分int Partition2(int *A, int p, int r, int f){int i;//找到f的位置並讓它與A[r]交換for(i = p; i < r; i++){if(A[i] == f){swap(A[i], A[r]);break;}}return Partition(A, p, r);}//尋找數組A[p..r]中的第i大的元素,i是從1開始計數,不是從p開始int Select(int *A, int p, int r, int i){//如果數組中只有一個元素,則直接返回if(p == r)return A[p];//根據文中的演算法,找到中值的中值int f = Find(A, p, r);//以這個中值為主元的劃分,返回中值在整個數組A[1..len]的位置//因為主元是數組中的某個元素,劃分好是這樣的,A[p..q-1] <= f < A[q+1..r]int q = Partition2(A, p, r, f);//轉換為中值在在數組A[p..r]中的位置int k = q - p + 1;//與所尋找的元素相比較if(i == k)return A[q];else if(i < k)return Select(A, p, q-1, i);else//如果主元是數組中的某個元素,後面一半要這樣寫return Select(A, q+1, r, i-k);//但是如果主元不是數組中的個某個元素,後面一半要改成Select(A, q, r, i-k+1)}//數組A中,求從start到end這段的分位元。這一段有k個分位元,即第ks+1個分位到第ks+k個分位元void K_Quantile(int *A, int *B, int k, int start, int end, int ks){if(k == 0)return;//先找最中間的分位元int x = Select(A, start, end, ((k+1)/2)*t);//記錄這個分位元B[ks+(k+1)/2] = x;//以這個分位元為主元把數組分為兩段,調度的時候發現這一步沒什麼用,因為SELECT已經包含了分段的過程Partition2(A, start, end, x);//分別找前後兩個的分位元K_Quantile(A, B, (k-1)/2, start, (ks+(k+1)/2)*t, 0);K_Quantile(A, B, k/2, (ks+(k+1)/2)*t+1, end, ks+(k+1)/2);}int main(){int i, k;while(cin>>length_A>>k){if(length_A % k){cout<<"不能劃分為k個大小相等的集合"<<endl;continue;}t = length_A / k;//A是輸入數組int *A = new int[length_A+1];//B是輸出數組,記錄k-1個分位元int *B = new int[k];//構造隨機資料for(i = 1; i <= length_A; i++)A[i] = rand() % 100;//列印輸入數組Print(A, length_A);//求k分位元演算法K_Quantile(A, B, k-1, 1, length_A, 0);//列印輸出數組Print(B, k-1);}return 0;}