這節給的題目是從一串數字中尋找最大的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大的數。