题目:输入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]; }
当然采用这种思路是有所限制的,因为它改变了原先的数组,所以决定用这种方法前,最好想清楚。还有一种效率略低一点的算法,以后再来讲。