面试题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;
}

运行结果:







原文地址:https://www.cnblogs.com/javawebsoa/p/3212599.html