单独使用递归构建最小堆

时间:2019-07-07 21:01:50

标签: c recursion optimization heapsort

这是尝试使heapsort仅使用函数调用来构建堆。问题在于,完成该项目所需的时间太长,比在同一项目中编写的一个简单的冒泡排序要花费更长的时间,比冒泡的42个冒着100000个条目的降序需要更长的时间。 Mergesort和quicksort从未超过一秒钟。如果编译器未能进行尾部调用优化,则不要担心它会崩溃到100000。

它曾经崩溃过,有关实现的一些详细信息可以使尾部调用发生。已对降序,升序和随机分布的数据进行了测试。还进行了一些更改以补救速度太慢,外观模糊的原因。

int heap_sort_step(int v[], int len, int i){
    int nextl = 2 * i + 1, nextr = nextl + 1;
    char bfl = (nextl<len)|((nextr<len)<<1);

    switch(bfl)
    {
        case 3:
        case 2:
            while(v[i] > heap_sort_step(v, len, nextr))
                swap(v + i, v + nextr);
        case 1:
            while(v[i] > heap_sort_step(v, len, nextl))
                swap(v + i, v + nextl);
        default:
            return v[i];
    }

}

void heap_sort(int v[], int len){
    return (len > 1)?
                (heap_sort_step(v, len, 0),
                 heap_sort(v + 1, len - 1)):
            NULL;
}

heap_sort(int v [],int len)接受一个数组,并调整其大小,并使用heap_sort_step()为数组中的每个成员构建最小堆,并对堆进行排序。这里需要尾巴呼叫。

heap_sort_step(int v [],int len,int i)带有一个数组,它的大小和用于构建堆的索引,其方程式如nextl和nextr,2i + 1和2i + 2所示,从i开始=0。“ bfl”是一种优化技巧(通过使用ifs进行的较小改进),通过查看前面是否还有更多项来决定要转到哪个分支或只是返回当前值。该开关使用fallthrough,并在此处构建堆,3和2表示右边有东西(0b11和0b10),1表示左边有东西(0b01),默认行为是返回当前数字。它曾经写成:

int heap_sort_step(int v[], int len, int i){
    int nextl = (((i + 1) * 2) - 1), nextr = nextl + 1;
    if(nextl < len)
        while(v[i] > heap_sort_step(v, len, nextl))
            swap(v + i, v + nextl);

    if(nextr < len)
        while(v[i] > heap_sort_step(v, len, nextr))
            swap(v + i, v + nextr);

    return v[i];
}

对于递归步骤,它将当前数字与下一个函数调用的返回值进行比较,如果它更大,它将交换,如果不是,则会有一个级联返回到根。

>

在这一点上,我很确定这只是一个不好的实现,并且想知道这是否是由更改或其他原因造成的复杂性问题。是否可以使用相同的概念与mergesort和quicksort竞争?应该是O n(log(n)),对吧?

2 个答案:

答案 0 :(得分:0)

不考虑递归对性能的影响,以及如何依靠尾调用优化使heap_sort()函数成为非递归的问题,您的实现看起来并不像堆排序。堆排序可以这样描述:

  1. 将要排序的元素排列到堆中(天真的为O(n log n);周到的是O(n))
  2. 只要堆中至少有两个元素,(O(n)次迭代)
    1. 将head元素与最后一个元素交换。 (O(1))
    2. 将(逻辑)堆大小减少1。(O(1))
    3. 还原堆条件。 (O(登录n),如果操作正确)

当然,无论您是使用max-heap以升序排序还是使用min-heap以降序排序。

正如您在注释中所阐明的那样,您的方法是将数组排列为最小堆以获得最小元素,然后用数组的n-1个元素尾部重复。 这不是堆排序,而是选择排序的一种变化。如果实施得当,它将花费O(n 2 ),因为每个堆构建步骤必须至少访问n / 2个非叶子节点,因此花费O(n)。

答案 1 :(得分:0)

进行一些更改后,它就像一种魅力,这是解决方法:

它不完整:

对于每一项,堆都是一遍又一遍地形成的,因此,正如约翰所指出的那样,这就像一个奇怪的选择排序。

完成了两个新功能:

void sift(int v[], int len, int i){
    int nextl = 2 * i + 1, nextr = nextl + 1;
    int next  = 0;

    if(nextr < len)
        if(v[i] > v[nextr]){
            if(v[nextr] < v[nextl])
                swap(v + i, v + (next = nextr));
            else
                swap(v + i, v + (next = nextl));
        }
    if(nextl < len)
        if(v[i] > v[nextl])
            swap(v + i, v + (next = nextl));

    return (next == 0) ? NULL: sift_min(v, len, next);
}

还原堆。在切换第一个和最后一个后,它会沿着一条路径在树上交换每个较小的数字。

void reverse(int v[], int len){
    int i;
    for(i = 0; i < len/2; i++)
        swap((v + i), (v + (len - (i + 1))));
}

在排序之后反转数组,因为在不知道第二步的情况下,使用minheap,排序以降序进行,因此可以使数组升序。

主块中有不必要的递归:

void heap_sort(int v[], int len){
    return (len > 1)?
                (heap_sort_step(v, len, 0),
                 heap_sort(v + 1, len - 1)):
            NULL;
}

被替换为

void heap_sort(int v[], int len){

    heapify(v, len, 0);//this is heap_sort_step with a new name

    int templen = len
    while(templen > 1){
        swap(v, v + --templen);
        sift(v, templen, 0);
    }

    reverse(v, len);
}

最终代码为:

void swap(int* ref1, int* ref2){
    int temp = *ref1;
    *ref1 = *ref2;
    *ref2 = temp;
}

int heapify(int v[], int len, int i){
    int nextl = 2 * i + 1, nextr = nextl + 1;

    if(nextr < len)
        while(v[i] > heapify(v, len, nextr))
            swap(v + i, v + nextr);

    if(nextl < len)
        while(v[i] > heapify(v, len, nextl))
            swap(v + i, v + nextl);

    return v[i];
}

void sift(int v[], int len, int i){
    int nextl = 2 * i + 1, nextr = nextl + 1;
    int next  = 0;

    if(nextr < len)
        if(v[i] > v[nextr]){
            if(v[nextr] < v[nextl])
                swap(v + i, v + (next = nextr));
            else
                swap(v + i, v + (next = nextl));
        }
    if(nextl < len)
        if(v[i] > v[nextl])
            swap(v + i, v + (next = nextl));

    return (next == 0) ? NULL: sift(v, len, next);
}

void reverse(int v[], int len){
    int i;
    for(i = 0; i < len/2; i++)
        swap((v + i), (v + (len - (i + 1))));
}

void heap_sort(int v[], int len){

    heapify(v, len, 0);

    int templen = len;

    while(templen > 1){
        swap(v, v + (--templen));
        sift(v, templen, 0);
    }

    reverse(v, len);
}

Lengthy,但是它仍然以递归方式构建堆并且速度很快。可以使用maxheap更快地进行,当然可以消除递归。对于花费一分钟以上的相同样本,快速测试平均得出的结果不到0.1秒。