[編程之美] 2.5 尋找最大的K個數

這節給的題目是從一串數字中尋找最大的K個數,而且考慮數據量比較大的情況。

解法一首先考慮了快速排序和堆排序,但是,它們對所有的數據都進行了排序,然後考慮使用部分排序算法,如選擇排序和交換排序,它們能夠從一串數字中選擇前K個,但是,效率依舊不高。

解法二使用了快速排序算法,在快速排序算法中,用一個樞軸將數列分成了兩個部分,左邊的比樞軸小,右邊的比樞軸大,然後再分別對兩個數列進行遞歸,在這裏,用樞軸來分的時候,左邊的比樞軸大,右邊的比樞軸小,當樞軸左邊的元素個數大於或者等於K,那麼,就返回左邊數列的最大的K個數,當樞軸左邊的元素個數n小於K,就返回左邊數列和右邊數列的最大的n-k-1個數。書中在實現的時候用到了兩個數組,這裏,我就按照快速排序的過程實現一遍。

#include <iostream>
#include <cstdlib>
#include <ctime>
#include <vector>
#include <iterator>
using namespace std;

vector<int>::iterator find_k(vector<int>::iterator beg, vector<int>::iterator end, int k)
{
	vector<int>::difference_type n = k;
	if(end - beg <= n) {
		return end;
	}
	
	vector<int>::iterator left = beg, right = end - 1;
	srand(time(NULL));
	int index = rand() % n;
	iter_swap(beg, beg + index);
	while(left < right) {
		while(*right < *left && left < right)
			--right;
		if(left < right) {
			iter_swap(left, right);
		}
		while(*left > *right && left < right)
		    ++left;
		if(left < right) {
			iter_swap(left, right);
		}
	}
	n = left - beg;
	if(n + 1 >= k)
	    return find_k(beg, left + 1, k);
	else
	    return find_k(left + 1, end, k - n - 1);
}

int main()
{
	int arr[] = {5, 3, 8, 1, 2, 7, 6, 9};
	vector<int> ivec(arr, arr + 8);

        int k = 5;
	vector<int>::iterator iter = find_k(ivec.begin(), ivec.end(), k);
	copy(ivec.begin(), iter, ostream_iterator<int>(cout, " "));

	return 0;
}

上述的代碼跟快速排序很像吧?爲了使樞軸隨機,不一定是第一個元素,代碼使用了隨機數生成函數,將第一個元素與後面的某一個元素進行調換。代碼執行結果:


解法三是採用跟解法二類似的二分法,從二進制的角度看,將整數從高位到低位某位爲0或者1進行二分。

解法四中,首先提出了一個問題,上面的方法都需要對數據訪問多次,如果數據量很大的情況下,數據無法全都裝入到內存中,會對效率造成很大的影響。

於是,就要求儘可能少地遍歷所有數據。

首先,假設數據的前K個數據就是最大的K個數據,如果第K+1個數據比前K個數據的最小值小,那麼前K+1個元素的最大的K個元素就是前K個元素,如果第K+1個數據比前K個數據的最小值大,那麼,前K+1個元素的最大的K個元素就是將前K個元素中最小的元素剔除掉就是了。這樣重複操作,遍歷完所有的元素後,最大的K個數據也就出來了,而且只遍歷了一遍數據。按照上述方案,可以得到以下代碼:

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

vector<int>::iterator min_iter(vector<int>::iterator beg, vector<int>::iterator end)
{
	vector<int>::iterator m = beg;

	++beg;
	while(beg != end) {
		if(*beg < *m) {
			m = beg;
		}
		++beg;
	}

	return m;
}

void find_k(vector<int>::iterator beg, vector<int>::iterator end, int k)
{
	vector<int>::difference_type n = k;
	if(end - beg <= n) {
		return;
	}

	vector<int>::iterator iter = beg + k;
	while(iter != end) {
		vector<int>::iterator m = min_iter(beg, beg + k);
		if(*iter > *m) {
			iter_swap(iter, m);
		}
		++iter;
	}
}

int main()
{
	int arr[] = {5, 3, 8, 1, 2, 7, 6, 9};
	vector<int> ivec(arr, arr + 8);
	
	int k = 5;
	find_k(ivec.begin(), ivec.end(), k);
	if(ivec.size() < k) {
		copy(ivec.begin(), ivec.end(), ostream_iterator<int>(cout, " "));
	}
	else {
		copy(ivec.begin(), ivec.begin() + k, ostream_iterator<int>(cout, " "));
	}

	return 0;
}

思路上面已經解釋了,代碼就不做過多解釋。

以上代碼只需要遍歷一次所有的元素,主要的瓶頸就在於min_iter()求前K個元素的最小值上,那麼,能夠快速地得到前K個元素的最小值嗎?

可以,使用堆,對前K個元素建小頂堆,那麼就能夠快速得到前K個元素的最小值了。這裏只給出核心函數:

void find_k(vector<int>::iterator beg, vector<int>::iterator end, int k)
{
	vector<int>::difference_type n = k;
	if(end - beg <= n) {
		return;
	}
	
	make_heap(beg, beg + n, greater<int>());
	vector<int>::iterator iter = beg + n;
	while(iter != end) {
		if(*iter > *beg) {
			pop_heap(beg, beg + n, greater<int>());
			iter_swap(iter, beg + n - 1);
			push_heap(beg, beg + n, greater<int>());
		}
		++iter;
	}
}

解法五首先提出了一種使用計數的方式,對每個值出現的次數進行計數,然後再從高到低得到最大的K個數,但是,這種方法僅適用於元素時正整數且取值範圍不大的數據。


擴展問題:

1 如果需要找出N個數中最大的K個不同的浮點數呢?注意這裏的“不同”兩個字。

當然,最簡單的辦法就是對N個數進行排序,最後相等的數字相鄰存放,然後從高到低遍歷,遇到相等的不進行計數,最後就得到了最大的K個數。

如果可以對數列進行修改,可以先進行預處理,把相同的數據剔除掉,然後求得最大的K個數,不過,預處理的代價也很高。

2 如果是找到第k到m(0<k<=m<=n)大的數呢?

最簡單的辦法就是利用本節的方法,找到最大的k-1個數和最小的m-1個數,剩下的就是第k到m大的數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章