一、问题
有很多无序的数,假定它们各不相等,从中找出最大的K个数。
问题分析:
输入:N个数;K。
输出:N个数中最大的K个数,这K个数并不需要是有序的,只需为数组中最大的K个数即可。
约束:N个数各不相等。
二、解法
解法一 采用排序算法类
A.数组全排序:
可以对数组进行快速排序或堆排序(O(n*lgn)),然后获得最大的K个数(O(k))。
时间复杂度:O(n*lgn) + O(k)=O(n*lgn)(k < n)。
缺点:算法不仅对最大K个数排序,也对其他的N-K个数排序,后面的方法可以避免。
B.数组K个数排序:
可以进一步优化:由于我们只需要最大的K个数,对于其他的n-k个数的排序我们实际上是不需要进行的。因此可以修改或改用其他的排序算法,可使用选择排序和交换排序,每次排序时只针对最大的K个数,减少对其余n-k个数的排序计算量。
时间复杂度:O(n*k)。当 k < lgn 时,该算法要优于快速排序。
缺点:算法对最大的K个数排序,后面的方法可以避免。
C.数组排序分组:(利用快速排序算法思想)
继续进行优化:如果我们能够做到对最大的K个数不进行排序,剩下n-k个数也不进行排序,这时候排序算法应该是最优的,实际上我们只找出了区分K和N-K个数的数组临界点,并然K个数和n-K个数分别位于数组临界点的两端。可以考虑对快速排序算法进行改进以解决我们的问题。
时间复杂度:算法平均时间复杂度O(n*lgk)。
优化:
1.算法可以对递归进行封装,从而考虑k < 0时返回-1,k >= N时返回数组最后一个索引。
2.partition部分算法采用数组最后一个值作为pivot,将数组分成两部分,若随机选取枢纽,可达到线性期望时间O(n)。
缺点:
最后一种从快速排序演化来的数组排序分组来获得最大的K个数算法是最高效的。以上三种方法均有一个缺点:数组必须一次性存储全部的数据到内存,当数据规模较大时由於硬件本身内存不够约束此种方法无法使用。
算法步骤:(摘自《编程之美》)在本问题中,假设 N 个数存储在数组 S 中,我们从数组 S 中随机找出一个元素 X,把数组分为两部分 Sa 和 Sb。
Sa 中的元素大于等于 X,Sb 中元素小于 X。这时,有两种可能性:
1. Sa中元素的个数小于K,Sa中所有的数和Sb中最大的K-|Sa|个元素(|Sa|指Sa中元素的个数)就是数组S中最大的K个数。
2. Sa中元素的个数大于或等于K,则需要返回Sa中最大的K个元素。
算法C实现:
1.排序采用从大到小排序以从数组得到最大的K个数,采用快速排序的partition算法根据这种排序方式实现(另一种是从小到大排序)。
3.算法关键在于找到递归的终止条件,这点和原始的快速排序是不同的。
/**
* @file find_large_numbers.c
* @brief find largest k numbers in a given array.
* @author chenxilinsidney
* @version 1.0
* @date 2015-02-04
*/
#include <stdlib.h>
#include <stdio.h>
// #define NDEBUG
#include <assert.h>
// #define NDBG_PRINT
#include "debug_print.h"
typedef int TYPE;
#define MAX_COUNT 10000000
TYPE array[MAX_COUNT] = {0};
/**
* @brief select the last element as a pivoit.
* Reorder the array so that all elements with values less than the pivot
* come before the pivot, while all elements with values less than the pivot
* come after it (equal values can go either way). After this partitioning, the
* pivot is in its final position.
*
* @param[in,out] array input and output array
* @param[in] index_begin the begin index of the array(included)
* @param[in] index_end the end index of the array(included)
*
* @return the position of the pivot(index from the array)
*/
TYPE partition(TYPE* array, TYPE index_begin, TYPE index_end)
{
/// pick last element of the array as the pivot
TYPE pivot = array[index_end];
/// index of the elments that not greater than pivot
TYPE i = index_begin - 1;
TYPE j, temp;
/// check array's elment one by one
for (j = index_begin; j < index_end; j++) {
if (array[j] >= pivot) {
/// save the elements not less than pivot to left index of i.
i++;
temp = array[j];
array[j] = array[i];
array[i] = temp;
}
}
/// set the pivot to the right position
array[index_end] = array[++i];
array[i] = pivot;
/// return the position of the pivot
return i;
}
/**
* @brief find the last index of the input array for largets N numbers.
* the numbers that index is before the last index is the largest N numbers.
*
* @param[in,out] array input and output array
* @param[in] index_begin the begin index of the array(included)
* @param[in] index_end the end index of the array(included)
* @param[in] count the count of the largest numbers.
*/
TYPE find_quick_sort(TYPE* array, TYPE index_begin, TYPE index_end, TYPE count)
{
assert(count > 0);
if (index_begin < index_end) {
/// get pivot by partition
TYPE index_pivot = partition(array, index_begin, index_end);
TYPE first_array_count = index_pivot - index_begin + 1;
DEBUG_PRINT_VALUE("%d", count);
DEBUG_PRINT_VALUE("%d", index_begin);
DEBUG_PRINT_VALUE("%d", index_end);
DEBUG_PRINT_VALUE("%d", index_pivot);
if (first_array_count < count)
/// find the other numbers in second part of the array
return find_quick_sort(array, index_pivot + 1, index_end,
count - first_array_count);
else if (first_array_count > count)
/// still find N numbers in first part of the array
return find_quick_sort(array, index_begin, index_pivot - 1,
count);
else
/// just find N numbers
return index_pivot;
} else {
return index_begin;
}
}
int main(void) {
/// read data to array
TYPE count = 0;
while(count < MAX_COUNT && scanf("%u\n", array + count) == 1) {
++count;
}
/// find largest N numbers
TYPE N = 15;
TYPE i, last_index;
if ((last_index = find_quick_sort(array, 0, count - 1, N)) >= 0) {
printf("get the largest %d numbers:\n", N);
DEBUG_PRINT_VALUE("%d", last_index);
for (i = 0; i <= last_index; i++) {
printf("%d ", array[i]);
}
} else {
printf("can not get the largest N numbers.\n");
}
return EXIT_SUCCESS;
}
D. 大小为K的小根堆:(堆排序算法思想)
维护一个大小为K的小根堆,此时堆顶元素是这K个元素中最小的一个元素,即第K个元素。遍历一次所有数据后得到的这个堆的K个元素即这个序列中最大的K个数。
时间复杂度:O(n*lgk),与前一中方法一致,算法效率不变。但是空间复杂度为O(k),因此不必使序列的所有的元素都存放到内存中,只需遍历一次序列即可,可分段遍历载入内存,克服了上一种方法的缺陷。
算法步骤:
1.利用遍历序列时数组前K个数建立一个大小为K的小根堆;
2.遍历序列时数组的剩余的每一个元素与小根堆堆顶做比较:如果该元素比堆顶元素小,则忽略该元素并继续遍历下一个元素;如果该元素比堆顶元素大,则用该元素替换堆顶元素,并调整堆使之满足堆的性质;
3.遍历结束后得到的小根堆的K个元素即问题的解。
算法C实现:
/**
* @file find_large_numbers_heap.c
* @brief find largest k numbers in a given array.
* @author chenxilinsidney
* @version 1.0
* @date 2015-02-04
*/
#include <stdlib.h>
#include <stdio.h>
// #define NDEBUG
#include <assert.h>
#include "heap.h"
// #define NDBG_PRINT
#include "debug_print.h"
typedef int TYPE;
#define MAX_COUNT 10000000
TYPE array[MAX_COUNT] = {0};
/**
* @brief find the last index of the input array for largets N numbers.
* the numbers that index in the array is before the last index is
* the largest N numbers.
*
* @param[in,out] array input and output array
* @param[in] array_length array length
* @param[in] count the count of the largest numbers.
*/
void find_heap_sort(TYPE* array, TYPE array_length, TYPE count)
{
assert(array != NULL && array_length > 0 && count > 0);
/// array need not to adjust if count larger than or equal to array length
if (count >= array_length)
return;
/// build heap with first count of the array
Heap heap;
BuildHeap(&heap, count, array - 1, count);
/// adjust heap with other elements
TYPE i, element;
for (i = count; i < array_length; i++) {
HeapGet(&heap, &element);
if (array[i] > element) {
heap.data[1] = array[i];
Heapify(heap.data, 1, count);
}
}
}
int main(void) {
/// read data to array
TYPE count = 0;
while(count < MAX_COUNT && scanf("%u\n", array + count) == 1) {
++count;
}
/// find largest N numbers
TYPE N = 15;
TYPE i;
find_heap_sort(array, count, N);
printf("get the largest %d numbers:\n", N);
for (i = 0; i < N; i++) {
printf("%d ", array[i]);
}
return EXIT_SUCCESS;
}