例如:我有一个10个元素的未排序列表A.我需要从k
到i
的{{1}}个连续元素的子列表。
i+k-1
答案 0 :(得分:2)
如果指定了i
和k
,则可以使用特殊版本的快速排序,停止对i .. i+k
范围之外的数组部分进行递归。如果可以修改数组,请执行此部分排序,如果无法修改数组,则需要进行复制。
以下是一个例子:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// Partial Quick Sort using Hoare's original partition scheme
void partial_quick_sort(int *a, int lo, int hi, int c, int d) {
if (lo < d && hi > c && hi - lo > 1) {
int x, pivot = a[lo];
int i = lo - 1;
int j = hi;
for (;;) {
while (a[++i] < pivot)
continue;
while (a[--j] > pivot)
continue;
if (i >= j)
break;
x = a[i];
a[i] = a[j];
a[j] = x;
}
partial_quick_sort(a, lo, j + 1, c, d);
partial_quick_sort(a, j + 1, hi, c, d);
}
}
void print_array(const char *msg, int a[], int count) {
printf("%s: ", msg);
for (int i = 0; i < count; i++) {
printf("%d%c", a[i], " \n"[i == count - 1]);
}
}
int int_cmp(const void *p1, const void *p2) {
int i1 = *(const int *)p1;
int i2 = *(const int *)p2;
return (i1 > i2) - (i1 < i2);
}
#define MAX 1000000
int main(void) {
int *a = malloc(MAX * sizeof(*a));
clock_t t;
int i, k;
srand((unsigned int)time(NULL));
for (i = 0; i < MAX; i++) {
a[i] = rand();
}
i = 20;
k = 10;
printf("extracting %d elements at %d from %d total elements\n",
k, i, MAX);
t = clock();
partial_quick_sort(a, 0, MAX, i, i + k);
t = clock() - t;
print_array("partial qsort", a + i, k);
printf("elapsed time: %.3fms\n", t * 1000.0 / CLOCKS_PER_SEC);
t = clock();
qsort(a, MAX, sizeof *a, int_cmp);
t = clock() - t;
print_array("complete qsort", a + i, k);
printf("elapsed time: %.3fms\n", t * 1000.0 / CLOCKS_PER_SEC);
return 0;
}
使用包含100万个随机整数的数组运行此程序,从偏移量20开始提取排序数组的10个条目,得出以下结果:
extracting 10 elements at 20 from 1000000 total elements
partial qsort: 33269 38347 39390 45413 49479 50180 54389 55880 55927 62158
elapsed time: 3.408ms
complete qsort: 33269 38347 39390 45413 49479 50180 54389 55880 55927 62158
elapsed time: 149.101ms
与整个阵列的排序相比,它确实要快得多( 20x 到 50x ),即使只是选择了一个简单的枢轴。尝试多次运行,看看时间如何变化。
答案 1 :(得分:1)
您可以使用Quickselect或堆选择算法来获取i+k
最小的项目。 Quickselect就地工作,但它会修改原始数组。如果项目列表大于内存中的项目列表,它也将无法工作。 Quickselect是O(n),但具有相当高的常数。当您选择的项目数量只占项目总数的一小部分时,堆选择算法会更快。
堆选择算法背后的想法是使用第一个i+k
项初始化最大堆。然后,迭代其余项目。如果项目小于max-heap上的最大项目,则从max-heap中删除最大项目,并将其替换为新的较小项目。完成后,堆上有第一个i+k
项,顶部有最大的k项。
代码非常简单:
heap = new max_heap();
Add first `i+k` items from a[] to heap
for all remaining items in a[]
if item < heap.peek()
heap.pop()
heap.push(item)
end-if
end-for
// at this point the smallest i+k items are on the heap
这需要O(i + k)额外内存,最坏情况下运行时间为O(n log(i + k))。当(i+k)
小于n
的约2%时,它通常会优于Quickselect。
有关此内容的更多信息,请参阅我的博文When theory meets practice。
顺便说一下,您可以根据i
稍微优化一下内存使用量。也就是说,如果数组中有十亿个项目并且您需要项目999,999,000到999,999,910,则上面的标准方法将需要一个巨大的堆。但是您可以将该问题重新转换为需要选择最后1,000个项目中最小的一个的问题。然后你的堆成为1,000个项目的最小堆。只需要一点点数学来确定哪种方式需要最小的堆。
当然,如果您想要600,000,000到600,000,010之类的物品,这并不会有多大帮助,因为您的堆中仍有4亿个物品。
但是,对我来说,如果时间不是很大的问题,你可以使用Floyd的算法就地在数组中构建堆,弹出第一个i
项就像你对堆排序一样,下一个k
项是你正在寻找的。这将需要恒定的额外空间和O(n +(i + k)* log(n))时间。
考虑到这一点,您可以使用一堆(i + k)项(如上所述)就地实现堆选择逻辑。实施它会有点棘手,但它不需要任何额外的空间,并且具有相同的运行时间O(n * log(i + k))。
请注意,两者都会修改原始数组。
答案 2 :(得分:0)
一个想法可能是扫描你的数组中更大或相等数量的i和更小或相等数量的i + k并将它们添加到另一个列表/容器中。
这将带你O(n)并给出你需要的无序数字列表。然后你对那个列表O(nlogn)进行排序,你就完成了。
对于非常大的数组,此方法的优点是您将对较小的数字列表进行排序。 (鉴于k相对较小)。
答案 3 :(得分:0)
您可以做的一件事就是修改heapsort,这样您就可以先创建堆,然后弹出第一个i
元素。从堆中弹出的下一个k
元素将是您的结果。丢弃剩余的n - i - k
元素,让算法提前终止。
结果将显示在O((i + k) log n)
O(n log n)
中,但i
和k
的相对较低值会显着提高。