[置顶] 最小的k个数

题目:输入n个数,找出其中最小的n个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字为1,2,3,4.

拿到这道题最显而易见的思路就是把这几个数排序,然后直接输出前k个数就OK了。用快排的话,它的时间复杂度为O(nlogn),但还有更快的算法。

我们可以借助快速排序中的partition函数来完成,这个函数是用来将一个数组中的数分为两部分,左边的数均小于给定的值,右边的数均大于给定的值,我在这里贴出partition的算法:

void Swap(int *e1, int *e2)
{
	int tmp = *e1;
	*e1 = *e2;
	*e2 = tmp;
}

int Media_3(int arr[], int start, int end)
{
	//返回头中尾三个数的中位数的下标

	int mid = (end - start) / 2 + start;

	if (arr[start] > arr[mid])
		Swap(&arr[start], &arr[mid]);
	if (arr[start] > arr[end])
		Swap(&arr[start], &arr[end]);
	if (arr[mid] > arr[end])
		Swap(&arr[mid], &arr[end]);

	return mid;
}

int partition(int arr[], int len, int start, int end)
{
	int small, index;

	if (NULL == arr || len <= 0 || start < 0 || end >= len)
		return -1;
	
	index = Media_3(arr, start, end);
	Swap(&arr[index], &arr[end]);
	small = -1;
	for (index = start; index < end; ++index)
	{
		if (arr[index] < arr[end])
		{
			++small;
			if (index != small)
				Swap(&arr[index], &arr[small]);
		}
	}
	++small;
	Swap(&arr[small], &arr[end]);

	return small;
}

partition返回的是一个下标值,并且,在这个数组中,此下标左边的值均小于这个下标位置对应的值,右边的值均大于这个下标对应的值,即将数组分成了两部分,左边小,右边大,分界线为这个下标对应的值。

有了这个函数,那么当我们需要查找最小k个数的时候,就可以想到,当partition返回的下标值为k-1时,那么左边的数必然均小于等于第k个最小的数,这时我们从下标0至k-1的k个数就是我们要查找的数了。下面是具体实现:

void SmallestKNumber(int input[], int n, int output[], int k)
{
	int index;
	int start = 0, end = n - 1;

	if (NULL == input || n <= 0 || NULL == output || k <= 0 || k >= n)
		return;
	
	index = partition(input, n, start, end);

	while (index != k - 1)
	{
		if (index < k - 1)
		{
			start = index + 1;
			index = partition(input, n, start, end);
		}
		else
		{
			end = index - 1;
			index = partition(input, n, start, end);
		}
	}
	
	for (index = 0; index < k; ++index)
		output[index] = input[index];
}

当然采用这种思路是有所限制的,因为它改变了原先的数组,所以决定用这种方法前,最好想清楚。还有一种效率略低一点的算法,以后再来讲。

你可能感兴趣的:(源代码,快速排序,C语言,最小K个数)