程式設計之美2:尋找最大的K個數
根據樓樓參加筆試或者面試的經驗而言,尋找最大的K個數這個問題,被問到已經不只兩三次了,所以樓樓決定認認真真地把這個問題寫一下,解法思想參照《程式設計之美》一書。
題目簡介
有很多無序的數,我們姑且假定他們各不相等,怎麼選出其中最大的K個數呢?
相關知識點
排序
題目解答
解法一:直接排序
這個解法是第一反應,假設有N個數,我們使用一個N個長度的陣列將其儲存下來,並且使用排序演算法將其從大到小依次排列。排序完成後,輸出前K個數。如果N不小,但是也不大,比如幾千什麼的,可以採用快速排序或者堆排序來完成。
程式碼:
#include <iostream>
using namespace std;
int findMaxN(int *pArray, int len);
int comp(const void*a , const void*b)
{
return *(int *)b - *(int *)a;
}
int main()
{
int a[] = {9, 8, 7, 6, 5, 4, 3, 11, 12, 13, 1, 28};
int K = 5;
int len = sizeof(a) / sizeof(int);
//利用快速排序法進行排序
qsort(a, len, sizeof(int ), comp);
for (int i = 0; i < K; i++)
{
cout << a[i] << " ";
}
system("pause");
}
複雜度分析:
堆排序或者快速排序平均的複雜度為
延伸:qsort()用法:
qsort(void*base, size_t num, size_t width, int(__cdecl*compare)(const void*,const void*))
第一個引數:待排序陣列首地址
第二個引數:陣列中待排序元素數量
第三個引數: 各元素的佔用空間大小
第四個引數:指向函式的指標,用於確定排序的順序。
以下為compare函式原型
compare( (void *) & elem1, (void *) & elem2 );
Compare 函式的返回值 | 描述 |
---|---|
小於 0 | elem1將被排在elem2前面 |
等於0 | elem1 等於 elem2 |
大於0 | elem1將被排在elem2後面 |
解法二:部分排序法
簡單分析一下,我們就能發現解法一的一個明顯不足之處,那就是我們將所有的元素都進行了排序,而題目要求只是尋找最大的K個數,也就是說我們只要將最大的K個數排好序就好了,沒必要將剩下的N-K個數也進行排序。
在這裡,我們可以使用快速排序來完成這個部分排序的功能。在快速排序中,每一輪都需要選定一個pivot,每一輪排序完成後,比pivot大的數都排在它前(後)面,而比pivot小的數都排在它的後(前)面。假設前面的序列為Sa,後面的序列為Sb,Sa的長度為n.
當
當
當
看程式碼:
#include <iostream>
using namespace std;
int kBig(int *pArray, int low, int high, int K);
int partion(int *pArray, int low, int high);
int main()
{
int a[] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17, 20};
for (int i = 0; i <= kBig(a, 0, sizeof(a)/sizeof(int), 2); i++)
{
cout << a[i] << " ";
}
system("pause");
}
//對前K大的數進行排序,並返回第K大數的下標
int kBig(int *pArray, int low, int high, int K)
{
int index, n;
if (low < high)
{
//對陣列進行劃分,並返回劃分的位置
index = partion(pArray, low, high);
n = index - low + 1; //Sa的個數
if (n == K) //如果恰好是K個的話,那麼返回下標就可以了
{
return index;
}
if (n < K) //如果Sa的個數不夠的話,那麼再從Sb中找K-n個
{
return kBig(pArray, index + 1, high, K - n);
}
if (n > K) //如果Sa的個數大於K的話,那麼就從Sa裡面返回K個
{
return kBig(pArray, low, index, K);
}
}
}
//快速排序的劃分函式並返回pivot的座標
int partion(int *pArray, int low, int high)
{
int i = low; int j = low;
int pivot = pArray[low];
for (; i < high, j < high;j++)
{
if (pArray[j] > pivot)
{
i++;
swap(pArray[i], pArray[j]);
}
}
swap(pArray[i], pArray[low]);
return i;
}
複雜度分析:
很顯然,相對解法一而言,解法二的複雜度為
解法三:堆排序法
就樓樓的面試經驗來看,如果這個問題你能答到堆排序演算法的話,這時候面試官就基本滿意了。這是他們想問的點,因為在他們問題裡會不斷地強調這個N是如何之大,記憶體受到限制之類的。比如如果N都是幾百萬的話,那用這麼大的陣列來儲存,這就是非常不明智地做法了。用堆就可以完美解決儲存問題。
#include <iostream>
using namespace std;
void buildMinHeap(int *pArray, int K);
void adjustHeap(int *pArray, int rootIndex, int heapSize);
int main()
{
int a[] = {9, 8, 7, 6, 5, 4, 3, 11, 12, 13, 1, 28};
int K = 5 ;
//建一個K個元素大小的最小堆
buildMinHeap(a, K);
//從第K個元素開始掃描,看有沒有比根節點更大的節點,若有則替換,並更新堆;若沒有比根節點大則掃描下一個元素,直到陣列結束
for (int i = K; i < sizeof(a) / sizeof(int); i++)
{
if (a[i] > a[0])
{
swap(a[i], a[0]);
adjustHeap(a, 0, K);
}
}
//打印出前K大的數,沒有排序。
for (int i = 0; i < K; i++)
{
cout << a[i] << " ";
}
system("pause");
}
//建一個K個元素大小的最小堆
void buildMinHeap(int *pArray, int K)
{
for (int i = (K - 2) / 2; i >= 0; i--)
{
adjustHeap (pArray, i, K);
}
}
void adjustHeap (int *pArray, int rootIndex, int heapSize)
{
int minIndex = rootIndex;
//左孩子節點
int leftIndex = 2 * rootIndex + 1;
//右孩子節點
int rightIndex = 2 * (rootIndex + 1);
//如果左孩子比根節點和右孩子節點小的話,則左孩子和根節點進行交換
if ((leftIndex < heapSize) && (rightIndex < heapSize) && (pArray[leftIndex] < pArray[rightIndex]) && (pArray[leftIndex] < pArray[rootIndex]))
{
minIndex = leftIndex;
}
if ((leftIndex < heapSize) && (rightIndex >= heapSize) && (pArray[leftIndex] < pArray[rootIndex]))
{
minIndex = leftIndex;
}
if ((rightIndex < heapSize) && (pArray[rightIndex] < pArray[leftIndex]) && (pArray[rightIndex] < pArray[rootIndex]))
{
minIndex = rightIndex;
}
if (minIndex != rootIndex)
{
//如果左孩子或者右孩子比根節點小的話,那麼就交換,並且重新調整以minIndex為根節點的子樹
swap(pArray[rootIndex], pArray[minIndex]);
adjustHeap(pArray, minIndex, heapSize);
}
}
複雜度分析:
解法四:計數排序法
分析一下上面的三種解法,時間複雜度都不是線性的,那我們會問存不存在一種線性的解法?事實上是存在的,但存在的這種解法存在限制。解法思想如下:
如果所有N個數都是正整數,且他們的取值範圍不大,我們知道最大的數是MAXN。那麼我們可以申請一個數組count[MAXN]來記錄每個數出現的次數。然後我們就可以找出最大的K個數。
看程式碼:
#include <iostream>
using namespace std;
int findMaxN(int *pArray, int len);
int main()
{
int a[] = {9, 8, 7, 6, 5, 4, 3, 11, 12, 13, 1, 28};
int K = 5;
int MAXN = findMaxN(a, sizeof(a) / sizeof(int));
//申請一個count陣列,記錄每一個數出現的次數
int *count = new int[MAXN + 1]();
for (int i = 0; i < sizeof(a) / sizeof(int); i++)
{
count[a[i]]++;
}
int index = MAXN;
int sumCount = 0;
for (;index >= 0; index--)
{
sumCount += count[index];
if (sumCount == K)
{
break;
}
}
//打印出最大的K個數
for (int i = MAXN; i >= index; i--)
{
if (0 != count[i])
{
cout << i << " ";
}
}
system("pause");
}
//找出一個數組中最大的值
int findMaxN(int *pArray, int len)
{
int MAXN = pArray[0];
for (int i = 1; i < len; i++)
{
if (pArray[i] > MAXN)
{
MAXN = pArray[i];
}
}
return MAXN;
}
複雜度分析:
同學們,有啥建議或者想法請給我留言哦~~~