面试题25:最小的K个数
方法一:对n个整数进行排序(快速排序或堆排序),取出前K个元素(最容易想到的最笨的方法,不可取)
时间复杂度:O(n*logn) + O(k) = O(n*logn)
采用快速排序的代码:
#include "stdafx.h" #include <iostream> using namespace std; //划分数组,找到枢轴元素下标,使得其左边的元素都比其小,右边的元素都比其大 int Partition(int nArr[], int nLength) { //初始状态下,选取数组的第一个元素作为枢轴元素 int nPivot = nArr[0]; //设置两个游标 int nLow = 0; int nHigh = nLength - 1; while (nLow < nHigh) { while (nLow < nHigh && nArr[nHigh] >= nPivot) { nHigh--; } nArr[nLow] = nArr[nHigh]; while (nLow < nHigh && nArr[nLow] <= nPivot) { nLow++; } nArr[nHigh] = nArr[nLow]; } nArr[nLow] = nPivot; return nLow; } void FastSort(int nArr[], int nLength) { if (nArr == NULL || nLength <= 0) { return; } int nMidIndex = Partition(nArr, nLength); FastSort(nArr, nMidIndex); FastSort(nArr + nMidIndex + 1, nLength - 1 - nMidIndex); } int _tmain(int argc, _TCHAR* argv[]) { int nArr1[8] = {4, 5, 1, 6, 2, 7, 3, 8}; int nArr2[8] = {4, 5, 2, 6, 2, 5, 3, 5}; int nArr3[8] = {1, 1, 1, 1, 1, 1, 1, 1}; while (1) { int k = 0; cout << "请输入最小数的个数:"; cin >> k; if (k == 0) { break; } FastSort(nArr1, 8); cout << "第一个数组的最小的" << k << "个数为:"; for (int i=0; i<k; i++) { cout << nArr1[i] << " "; } cout << endl; FastSort(nArr2, 8); cout << "第二个数组的最小的" << k << "个数为:"; for (int i=0; i<k; i++) { cout << nArr2[i] << " "; } cout << endl; FastSort(nArr3, 8); cout << "第三个数组的最小的" << k << "个数为:"; for (int i=0; i<k; i++) { cout << nArr3[i] << " "; } cout << endl; } system("pause"); return 0; }
运行结果:
采用堆排序的代码:
#include "stdafx.h" #include <iostream> using namespace std; //调整堆(大根堆)函数,待调整元素的下标nIndex, void AdjustHeap(int nHeap[], int nLength, int nIndex) { int key = nHeap[nIndex];//待调整元素的值 for (int i=2*nIndex+1; i<nLength; i=2*i+1) { if (i+1 < nLength && (nHeap[i] < nHeap[i+1]))//有右孩子且右孩子的值比左孩子大 { i++;//i指向表示较大孩子下标 } if (nHeap[i] < key)//不需要调整 { break; } nHeap[nIndex] = nHeap[i]; nIndex = i; } nHeap[nIndex] = key; } //堆排序,从小到大 void HeapSort(int nArr[], int nLength) { if (nArr != NULL && nLength > 0) { for (int i=nLength/2-1; i>=0; i--) { AdjustHeap(nArr, nLength, i);//调整为大根堆 } for (int j=nLength-1; j>=0; j--) { int temp = nArr[j]; nArr[j] = nArr[0]; nArr[0] = temp; AdjustHeap(nArr, j, 0); } } else { return ; } } int _tmain(int argc, _TCHAR* argv[]) { int nArr1[8] = {4, 5, 1, 6, 2, 7, 3, 8}; int nArr2[8] = {4, 5, 2, 6, 2, 5, 3, 5}; int nArr3[8] = {1, 1, 1, 1, 1, 1, 1, 1}; while (1) { int k = 0; cout << "请输入最小数的个数:"; cin >> k; if (k == 0) { break; } HeapSort(nArr1, 8); cout << "第一个数组的最小的" << k << "个数为:"; for (int i=0; i<k; i++) { cout << nArr1[i] << " "; } cout << endl; HeapSort(nArr2, 8); cout << "第二个数组的最小的" << k << "个数为:"; for (int i=0; i<k; i++) { cout << nArr2[i] << " "; } cout << endl; HeapSort(nArr3, 8); cout << "第三个数组的最小的" << k << "个数为:"; for (int i=0; i<k; i++) { cout << nArr3[i] << " "; } cout << endl; } system("pause"); return 0; }
运行结果:
方法二:使用选择排序或冒泡排序,进行K次选择,可得到最小的k个数
时间复杂度:O(n*k)
选择排序代码:
#include "stdafx.h" #include <iostream> using namespace std; //利用选择排序找到最小的k个数,进行k次选择即可 void SelectSort_KMinNum(int nArr[], int nLength, int k) { if (nArr == NULL || nLength <= 0 || k>nLength) { cout << "输入有误!" << endl; return; } int nMin = 0; int nMinIndex = 0; for (int i=0; i<k; i++)//进行k次选择 { nMin = nArr[i]; nMinIndex = i; for (int j=i+1; j<nLength; j++) { if (nArr[j] < nMin) { nMin = nArr[j]; nMinIndex = j; } } if (i != nMinIndex) { int temp = nArr[i]; nArr[i] = nArr[nMinIndex]; nArr[nMinIndex] = temp; } cout << nArr[i] << " "; } cout << endl; } int _tmain(int argc, _TCHAR* argv[]) { int nArr1[8] = {4, 5, 1, 6, 2, 7, 3, 8}; int nArr2[8] = {4, 5, 2, 6, 2, 5, 3, 5}; int nArr3[8] = {1, 1, 1, 1, 1, 1, 1, 1}; while (1) { int k = 0; cout << "请输入最小数的个数:"; cin >> k; if (k == 0) { break; } cout << "第一个数组的最小的" << k << "个数为:"; SelectSort_KMinNum(nArr1, 8, k); cout << "第二个数组的最小的" << k << "个数为:"; SelectSort_KMinNum(nArr2, 8, k); cout << "第三个数组的最小的" << k << "个数为:"; SelectSort_KMinNum(nArr3, 8, k); } system("pause"); return 0; }
运行结果:
冒泡排序代码:
#include "stdafx.h" #include <iostream> using namespace std; //利用冒泡排序找到最小的k个数,进行k次选择即可 void BubbleSort_KMinNum(int nArr[], int nLength, int k) { if (nArr == NULL || nLength <= 0 || k>nLength) { cout << "输入有误!" << endl; return; } for (int i=1; i<=k; i++)//进行k次冒泡过程 { for (int j=0; j<nLength-i; j++) { if (nArr[j] < nArr[j+1]) { int temp = nArr[j]; nArr[j] = nArr[j+1]; nArr[j+1] = temp; } } cout << nArr[nLength-i] << " "; } cout << endl; } int _tmain(int argc, _TCHAR* argv[]) { int nArr1[8] = {4, 5, 1, 6, 2, 7, 3, 8}; int nArr2[8] = {4, 5, 2, 6, 2, 5, 3, 5}; int nArr3[8] = {1, 1, 1, 1, 1, 1, 1, 1}; while (1) { int k = 0; cout << "请输入最小数的个数:"; cin >> k; if (k == 0) { break; } cout << "第一个数组的最小的" << k << "个数为:"; BubbleSort_KMinNum(nArr1, 8, k); cout << "第二个数组的最小的" << k << "个数为:"; BubbleSort_KMinNum(nArr2, 8, k); cout << "第三个数组的最小的" << k << "个数为:"; BubbleSort_KMinNum(nArr3, 8, k); } system("pause"); return 0; }
运行结果:
方法三
、计数排序 + 数组实现
适合数据量小的数据,不提倡使用
使用计数排序,另开辟一个数组,记录每个整数出现的次数,然后再从大到小取最大的 K 个。
代码:
#include "stdafx.h" #include <iostream> using namespace std; //利用计数+数组找到最小的k个数 void CountArr_KMinNum(int nArr[], int nLength, int k) { if (nArr == NULL || nLength <= 0 || k>nLength) { cout << "输入有误!" << endl; return; } int nMax = nArr[0]; for (int i=1; i<nLength; i++) { if (nMax < nArr[i]) { nMax = nArr[i]; } } int *pArr = new int[nMax+1];//开辟一个数组 memset(pArr, 0, (nMax+1)*sizeof(int)); for (int j=0; j<nLength; j++) { pArr[nArr[j]]++; } int nCount = 0; for (int z=0; z<nMax+1; z++) { if (pArr[z]>0) { while ((pArr[z]--)>0) { if (nCount < k) { cout << z << " "; nCount++; } else { break; } } } } delete []pArr; pArr = NULL; cout << endl; } int _tmain(int argc, _TCHAR* argv[]) { int nArr1[8] = {4, 5, 1, 6, 2, 7, 3, 8}; int nArr2[8] = {4, 5, 2, 6, 2, 5, 3, 5}; int nArr3[8] = {1, 1, 1, 1, 1, 1, 1, 1}; while (1) { int k = 0; cout << "请输入最小数的个数:"; cin >> k; if (k == 0) { break; } cout << "第一个数组的最小的" << k << "个数为:"; CountArr_KMinNum(nArr1, 8, k); cout << "第二个数组的最小的" << k << "个数为:"; CountArr_KMinNum(nArr2, 8, k); cout << "第三个数组的最小的" << k << "个数为:"; CountArr_KMinNum(nArr3, 8, k); } system("pause"); return 0; }
运行结果:
方法四、利用STL中的map保存每个数出现的次数,找到K个数
时间复杂度O(n*logn) 空间复杂度O(n)
注意:1、不能使用CMap实现,因为Cmap不能根据key自动为其排序;2、map内部是由红黑树实现的,每次插入都是logn,总的复杂度为n*logn。
代码:
#include "stdafx.h" #include <iostream> #include <map> using namespace std; //利用map计数找到最小的k个数 void MapCount_KMinNum(int nArr[], int nLength, int k) { if (nArr == NULL || nLength <= 0 || k>nLength) { cout << "输入有误!" << endl; return; } map<int,int> countMap; for (int j=0; j<nLength; j++) { if (countMap[nArr[j]]==0) { countMap[nArr[j]] = 1; } else { countMap[nArr[j]]++; } } int nCount = 0; for (map<int, int>::iterator itr=countMap.begin(); itr!=countMap.end(); itr++) { if (itr->second > 0) { while ((itr->second--) > 0) { if (nCount < k) { cout << itr->first << " "; nCount++; } else { break; } } } } cout << endl; } int _tmain(int argc, _TCHAR* argv[]) { int nArr1[8] = {4, 5, 1, 6, 2, 7, 3, 8}; int nArr2[8] = {4, 5, 2, 6, 2, 5, 3, 5}; int nArr3[8] = {1, 1, 1, 1, 1, 1, 1, 1}; while (1) { int k = 0; cout << "请输入最小数的个数:"; cin >> k; if (k == 0) { break; } cout << "第一个数组的最小的" << k << "个数为:"; MapCount_KMinNum(nArr1, 8, k); cout << "第二个数组的最小的" << k << "个数为:"; MapCount_KMinNum(nArr2, 8, k); cout << "第三个数组的最小的" << k << "个数为:"; MapCount_KMinNum(nArr3, 8, k); } system("pause"); return 0; }
运行结果:
方法五、维护一个大小为k的大根堆,初始化为数组中前k个元素,调整为大根堆。对于数组中的剩下的数,判断与堆顶的大小。如果比堆顶大,则不需要改变原来的堆;如果比堆顶小,要将其替换堆顶的元素,调整为大根堆。
时间复杂度: O (N * log2 K ),算法只需扫描所有的数一次,调整堆的时间复杂度为O(log2K)
空间复杂度:O(k),需一个大小为k 的堆。
代码:
#include "stdafx.h" #include <iostream> using namespace std; //调整堆(大根堆)函数,待调整元素的下标nIndex, void AdjustHeap(int nHeap[], int nLength, int nIndex) { int key = nHeap[nIndex];//待调整元素的值 for (int i=2*nIndex+1; i<nLength; i=2*i+1) { if (i+1 < nLength && (nHeap[i] < nHeap[i+1]))//有右孩子且右孩子的值比左孩子大 { i++;//i指向表示较大孩子下标 } if (nHeap[i] < key)//不需要调整 { break; } nHeap[nIndex] = nHeap[i]; nIndex = i; } nHeap[nIndex] = key; } //找到数组nArr中最小的k个数 int* MinKNum(int nArr[], int nLength, int k) { if (nArr != NULL && nLength > 0 && k <= nLength) { //维护一个大小为k的大根堆,初始化为数组的前k个元素 int *nHeap = new int[k]; for (int i=0; i<k; i++) { nHeap[i] = nArr[i]; } for (int t=k/2-1; t>=0; t--) { AdjustHeap(nHeap, k, t);//调整为大根堆 } for (int j=k; j<nLength; j++) { if (nArr[j] < nHeap[0])//剩下的元素依次与堆顶元素进行比较,若比其小则替换堆顶元素 { nHeap[0] = nArr[j]; AdjustHeap(nHeap, k, 0); } } return nHeap; } else { return NULL; } } int _tmain(int argc, _TCHAR* argv[]) { int nArr[8] = {4, 5, 1, 6, 2, 7, 3, 8}; //int nArr[8] = {1, 2, 3, 4, 5, 6, 7, 8}; //int nArr[8] = {2, 2, 2, 2, 2, 2, 2, 2}; int k = 0; while (1) { cout << "请输入最小数的个数:"; cin >> k; if (k == 0)//输入0表示程序结束 { break; } int *p_nHeap = MinKNum(nArr, 8, k); cout << "最小的" << k << "个数为:"; for (int i=0; i<k; i++) { cout << p_nHeap[i] << " "; } cout << endl; delete [] p_nHeap; p_nHeap = NULL; } system("pause"); return 0; }
运行结果: