排序算法的应用

使用堆排序求前k个最大(小)数

原理

        假设序列中有n个元素,取其前k个组成一个最大堆。由于最大堆的堆顶为序列中最大元素,所以组成的最大堆的堆顶是前k个元素中最大的元素。依次用第k+1到第n个元素与堆顶进行比较,如果比堆顶元素大,那么该元素肯定不会是前K个最小的元素;如果比堆顶小,那么堆顶的元素肯定不是前k个最小的元素,此时更新堆顶元素,并重新计算使新堆成为最大堆。依次进行,直到第n个元素结束,那么留在堆内的元素就是前k个最小的元素。

        对于求前k个最小的元素,思想与上面差不多。只不过要取前k个元素建成最小堆,剩余的元素依次与堆顶元素相比,大则更新堆顶,并重新排列堆;小则放弃。直到n个元素比较完成,留在堆中的就是前k个最大元素。

代码

/*
 count : 整个数组的长度
 k : 要获取前k个最小数
 */
void HeapSort(int a[],int count,int k){
    if(k > count)
        return;
    int b[k];
    int x = 0;
    for(x =0;x<k;x++){
        b[x] = a[x];
    }
    siftup(b, k);//先将这些数字变成最大堆
  //  printarray(b, k);
    for(x = k;x<count;x++){
        if(a[x] < b[0]){
            b[0] = a[x];
            AdjustAtPos(b,k,0);
        }
    }
    printarray(b, k);
}
        上述代码是求前k个最小值的代码,它的思路为:首先取a数组中前k个数组成b数组,并通过siftup()方法将b数组转换成最大堆。再从第k+1个元素开始,依次和堆顶元素比对,如果堆顶元素大,则更新堆顶元素,并重新计算移动堆顶元素,使新堆仍旧是最大堆。

siftup()如下:

void siftup(int a[],int count){
    int x = 0;
    if(count % 2 == 0){
        x = count/2-1;
    }else{
        x = (count-1)/2;
    }
    for(;x>=0;x--){
        AdjustAtPos(a, count, x);
    }
}

        前面的判断是用来寻找最后一个非叶子节点(如果二叉树根节点记为编号记为0,那么编号为k的节点其左右子节点的编号分别为2k+1,2k+2)。并依次调整每一个非叶子节点的位置。

AdjustAtPos()如下:

void AdjustAtPos(int a[],int count,int pos){
    int index = pos;
    while(2*pos+1 < count){
        if(a[pos] < a[2*pos+1])
            index = 2*pos+1;
        if(2*pos + 2<count){
            if(a[pos] < a[2*pos + 2]){
                index = 2*pos + 2;
            }
        }
        if(index != pos){
            swap(a, index, pos);
            pos = index;
        }else{
            break;
        }
        
    }
}

        while判断如果成立表明该节点肯定有左节点。

        第一个if判断如果成立表明当前节点的值小于其左节点的值,用index记录下该节点的编号。如果不成立,那么index就是pos。这个判断执行完毕之后,index存储的就是根节点和左节点中值比较大的节点的编号。

        第二次if判断成立的话,表明其有右节点。内层的判断逻辑与上面一样。

        上述代码执行完毕后,index指的就是根节点,左、右节点中值比较大的节点的编号(下标)。因此,如果index == pos成立,那么意味着根节点是最大的,不需要进行调整。如果不成立,并假设index记录的是左节点的下标,那么就需要交换左节点与根节点的值(swap()方法的作用),交换完毕之后左子树就不一定是最大堆了,又需要重新调用左子树,这就是pos = index的作用。

拓展

        在上面的求前k个最小数时,建立的是最大堆,直到序列的最后一个元素遍历结束后,堆顶仍旧是堆中最大的元素。因此堆顶是第k个最小的元素。利用这,就可以求出第k大或第k小的元素。

        求前k个最小元素或者第k小的元素,使用最大堆,堆的节点个数为k,堆顶的元素就是第k个最小的元素。求前k个最大元素或第k个最大元素,利用最小堆,堆的节点个数为k,堆顶的元素就是第k个最大的元素。






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值