經典算法總結之線性時間做選擇


問題:

輸入:一個包含n個(不同的)數的集合A和一個數i, 1 <= I <= n。

輸出:元素x∈A, 它恰大於A中其他的I – 1個元素(即求第k小數)。

本博文中尋找最大的K個數(TOP K算法)這篇文章也用了本文中的算法,大家可以參考。

三種算法:

1、 直接排序,輸出數組第i個元素即可, 時間復雜度為O(nlgn)

2、 這種算法,利用“快排的或者類似二分”的思想,每次以樞紐為界,分兩邊,每次只需處理一邊即可(拋棄另一邊),平均情況下的運行時間界為O(n),這種算法以期望時間做選擇。《算法都論》里是,在分治時用隨機數來選取樞紐(算法導論中偽代碼見圖),好吧,這是理論上的算法,它沒有考慮實際產生隨機數的開銷,事實上,效率一點也不高,已經測試過,產生隨機數花費的開銷真的很大,后邊我用更快的三數中值又實現了一遍,思想是一樣的,只是效率提高了。

C++完整代碼:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int partition(vector<int> &A,int p,int r){
	int x = A[r];
	int i=p-1;
	int temp;
	for(int j = p;j<r;++j){
		if(A[j]<=x){
			++i;
			swap(A[i],A[j]);
		}
	}
	swap(A[i+1],A[r]);
		return i+1;
}

inline int Random(int low, int high) {    
	return (rand() % (high - low + 1)) + low;
} 

int Randomized_Partition(vector<int> &kp, int low, int high) {    
	int i = Random(low, high);   
	swap(kp[high], kp[i]);   
	return partition(kp, low, high);
}

void randomized_quickSort(vector<int> &A,int p,int r){
	if(p<r){
		int q = Randomized_Partition(A,p,r);
		randomized_quickSort(A,p,q-1);
		randomized_quickSort(A,q+1,r);
	}
}

int randomized_select(vector<int> A,int p,int r,int i){
	if(p==r)
		return A[p];
	if(p>r) return -1;
	int q = Randomized_Partition(A,p,r);
	int k = q-p+1;
	if(i==k)
		return A[q];
	else if(i<k)
		return randomized_select(A,p,q-1,i);
	else return randomized_select(A,q+1,r,i-k);
}

void main(){
	int a[10] = {9,10,8,7,6,5,4,3,2,1};
	vector<int> A(a,a+10);
	cout<<randomized_select(A,0,9,5)<<endl;
} 

3、 第三種算法以最壞情況線性時間做選擇,最壞運行時間為O(n),這種算法基本思想是保證每個數組的划分都是一個好的划分,以5為基,五數取分,這個算法,算法導論沒有提供偽代碼,額,利用它的思想,可以快速返回和最終中位數相差不超過2的數,這樣的划分接近最優,基本每次都二分了(算法導論中步驟見圖)

/*利用中位數來選取樞紐元,這種方法最壞情況下運行時間是O(n) 
這里求的中位數是下中位數算法導論里沒有偽代碼,
寫起來很麻煩注意這里的查找到的中位數,
並不是真正意義上的中位數而是和真正中位數相差不超過2的一個數開始以為我寫錯了
,又看了算法導論,應該就是這個意思返回的是[x - 1, x + 2]的一個數,中位數是x從下邊的輸出中也可以看出:*/
#include<iostream>
#include<cstdio>
using namespace std; 
const int maxn = 14;//kp -> size
const int maxm = maxn / 5 + 1;//mid -> size
int kp[maxn];int mid[maxm]; //插入排序
void InsertionSort(int kp[], int n) {
	for (int j, i = 1; i < n; i++) { 
		int tmp = kp[i];        
		for (j = i; j > 0 && kp[j - 1] > tmp; j--) {    
			kp[j] = kp[j - 1];     
		}  
		kp[j] = tmp;  
	}
} //查找中位數, 保證每一個划分都是好的划分
int FindMedian(int kp[], int low, int high) {    
	if (low == high) {        
		return kp[low];    
	}    
	int index = low;//index初始化為low      
	//如果本身小於5個元素,這一步就跳過    
	if (high - low + 1 >= 5) {         //儲存中位數到mid[]      
		for (index = low; index <= high - 4; index += 5) {       
			InsertionSort(kp + index, 5);      
			int num = index - low;         
			mid[num / 5] = kp[index + 2];    
		}  
	}     //處理剩下不足5個的元素  
	int remain = high - index + 1;   
	if (remain > 0) {     
		InsertionSort(kp + index, remain);       
		int num = index - low;    
		mid[num / 5] = kp[index + (remain >> 1)];//下中位數 
	}    
	int cnt = (high - low + 1) / 5;    
	if ((high - low + 1) % 5 == 0) {      
		cnt--;//下標是從0開始,所以需要-1  
	}//存放在[0…tmp]   
	if (cnt == 0) {      
		return mid[0];    
	} else {        
		return FindMedian(mid, 0, cnt);     
	}
} int Qselect(int kp[], int low, int high, int k) {  
	int pivotloc = FindMedian(kp, low, high);    //這里有點不一樣,因為不知道pivotloc下標,所以全部都要比較   
	int i = low - 1, j = high + 1; 
	for (; ;) {     
		while (kp[++i] < pivotloc) {}   
		while (kp[--j] > pivotloc) {}     
		if (i < j)  swap(kp[i], kp[j]);    
		else break; 
	}     int num = i - low + 1; 
	if (k == num) return kp[i];    
	if (k < num) {     
		return Qselect(kp, low, i - 1, k);  
	} else { 
			return Qselect(kp, i + 1, high, k - num);  
	}
}
int main() {    
	int kp[maxn] = {10, 14, 8, 11, 7, 1, 2, 13, 3, 12, 4, 9, 6, 5};   
	for (int i = 0; i < maxn; i++) {      
		printf("中位數是:  %d\n", FindMedian(kp, 0, maxn - 1));       
		printf("第%d小的數是:  ", i + 1);     
		cout <<  Qselect(kp, 0, maxn - 1, i + 1) << endl << endl;   
	}    
	return 0;
}



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM