1. 程式人生 > >[CareerCup] 18.6 Smallest One Million Numbers 最小的一百萬個數字

[CareerCup] 18.6 Smallest One Million Numbers 最小的一百萬個數字

18.6 Describe an algorithm to find the smallest one million numbers in one billion numbers. Assume that the computer memory can hold all one billion numbers.

這道題讓我們在十億個數字中找到最小的一百萬個數字,而且限定了計算機只有能存十億個數字的記憶體。這題有三種解法,排序,最小堆,和選擇排序。

首先來看排序方法,這種方法簡單明瞭,就是把這十億個數字按升序排列,然後返回前一百萬個即可,時間複雜度是O(nlgn)。

然後來看最小堆做法,我們建立一個最大堆(大的數字在頂端),然後將前一百萬個數字加進去。然後我們開始遍歷剩下的數字,對於每一個數字,我們將其加入堆中,然後刪掉堆中最大的數字。遍歷接受後,我們就有了一百萬個最小的數字,時間複雜度是O(nlgm),其中m是我們需要找的數字個數。

最後我們來看選擇排序的方法,這種方法可以線上性時間內找到第i個最大或最小的數,如果數字都不是不同的,那麼我們可以在O(n)的時間內找到第i個最小的數字,演算法如下:

1. 隨機選取陣列中的一個數字當做pivot,然後以此來分割陣列,記錄分割處左邊的數字的個數。

2. 如果左邊正好有i個數字,那麼返回左邊最大的數字。

3. 如果左邊數字個數大於i,那麼繼續在左邊遞迴呼叫這個方法。

4. 如果左邊數字個數小於i,那麼在右邊遞迴呼叫這個方法,但是此時的rank變為i - left_size。

參見程式碼如下:

int partition(vector<int> &array, int
left, int right, int pivot) { while (true) { while (left <= right && array[left] <= pivot) ++left; while (left <= right && array[right] > pivot) --right; if (left >right) return left - 1; swap(array[left], array[right]); } }
int find_max(vector<int> &array, int left, int right) { int res = INT_MIN; for (int i = left; i <= right; ++i) { res = max(res, array[i]); } return res; } int selection_rank(vector<int> &array, int left, int right, int rank) { int pivot = array[rand() % (right - left + 1) + left]; int left_end = partition(array, left, right, pivot); int left_size = left_end - left + 1; if (left_size == rank + 1) return find_max(array, left, left_end); else if (rank < left_size) return selection_rank(array, left, left_end, rank); else return selection_rank(array, left_end + 1, right, rank - left_size); }

一旦找到了第i個最小的數字後,就可以遍歷整個陣列來找所有小於等於該數字的元素。當陣列有重複元素的話,需要修改一些地方,但是時間就不能保證是線性的了。其實也有演算法能線性時間內處理有重複的陣列,但是比較複雜,有興趣的請自行搜尋研究。