十大排序算法-堆排序 c语言实现

介绍

        堆排序是一种基于比较的排序算法,它利用堆这种数据结构进行排序。堆是一种特殊的完全二叉树,它的每个节点都大于等于(或小于等于)其子节点。堆排序的主要思想是将待排序的序列构造成一个大顶堆(或小顶堆),然后将堆顶元素与堆尾元素交换,将堆的大小减一,再调整堆,重复这个过程直到堆的大小为1。

代码实现

#define PTR_MATH(base,i,width) ( ((char*)base) + (i)*width )

        这是一个C语言的宏定义,用于实现指针的数学运算。它的目的是简化指针的偏移量计算,提高代码的可读性和可维护性。这个宏定义的实现原理是,通过将指针与一个整数相加,实现指针的偏移量计算。其中,base 是原始指针,i 是偏移量,width 是指针宽度。

//交换两段内存的数据
void lSwap(void* e1, void* e2, void* temp, int width){
    if(width<=0)
        return;

    void* temp_ = NULL;
    if(temp==NULL){
        temp_ = (void*)malloc(width);
    }else{
        temp_ = temp;
    }
    
    memcpy(temp_, e1, width);
    memcpy(e1, e2, width);
    memcpy(e2, temp_, width);

    if(temp==NULL)
        free(temp_);

}

这段C代码的目的是交换两段内存的数据。它接受四个参数:两个指向内存块的指针e1和e2,一个指向临时内存块的指针temp,以及一个表示内存块宽度的整数width。

实现原理:

  1. 首先,检查width是否小于等于0,如果是,则直接返回,不执行任何操作。

  2. 如果temp为空,则分配一个宽度为width的临时内存块,并将地址赋值给temp_。如果temp不为空,则将temp的值赋给temp_。

  3. 使用memcpy函数将e1中的数据复制到temp_中,然后将e2中的数据复制到e1中,最后将temp_中的数据复制到e2中。

  4. 如果temp为空,则释放分配的临时内存块。

用途:

这个函数可以用于交换两个结构体、数组或其他数据类型的内存块。例如,在实现一个链表交换节点数据的函数时,可以使用这个函数来交换两个节点的数据。

注意事项:

  1. 确保传入的指针e1和e2是有效的,否则可能导致程序崩溃。

  2. 确保传入的width是一个正整数,否则可能导致程序无法正确执行。

  3. 如果temp为空,则在函数内部分配的临时内存块需要在函数外部释放,以避免内存泄漏。

//构建堆
void heapify(void* base, int begin, int end,void*temp, int width, int(*compare)(const void* e1, const void* e2)){
    //比较回调函数为空或堆中仅有一个元素直接返回
    if(compare == NULL||end-begin<=1)
        return;

    //堆头
    int dad=begin;
    //第一个子节点
    int son=dad*2+1;

    while(1){
        //该节点不存在子节点
        if(son>=end)
            break;

        //判断当前父节点是否有两个子节点,如果有,则根据要构建大顶堆还是小顶堆取响应值
        if(son+1<end&&compare(PTR_MATH(base,son+1,width),PTR_MATH(base,son,width))>0)
            son++;

        //如果相应值大于或小于父节点,则交换值
        if(compare(PTR_MATH(base,son,width),PTR_MATH(base,dad,width))>0)
            lSwap(PTR_MATH(base,son,width),PTR_MATH(base,dad,width),temp,width);
        else
            break;

        //当前堆排序完成,排列以与父节点交换值的子节点为头的堆
        dad = son;
        son = dad*2+1;

    }

}

这段C代码定义了一个名为heapify的函数,用于构建堆。堆是一种特殊的二叉树,它的每个节点都大于等于(或小于等于)其子节点。在这个函数中,我们首先检查比较回调函数是否为空或者堆中是否仅有一个元素,如果是,则直接返回。否则,我们继续进行堆排序操作。

函数的参数包括:

  • base:指向堆的指针,用于访问堆中的元素。
  • begin:堆的起始位置(包含)。
  • end:堆的结束位置(不包含)。
  • temp:用于交换值的临时变量。
  • width:每个元素的字节数。
  • compare:比较回调函数,用于比较两个元素的大小。

函数的主要逻辑如下:

  1. 初始化堆头dadbegin,第一个子节点sondad*2+1
  2. 使用一个while循环,不断地将当前堆排序完成,直到子节点son超过堆的结束位置end
  3. 在循环中,首先检查子节点son是否存在,如果不存在,则跳出循环。
  4. 然后,判断当前父节点dad是否有两个子节点,如果有,则根据要构建大顶堆还是小顶堆取响应值。
  5. 如果相应值大于或小于父节点dad,则交换值。
  6. 当前堆排序完成,排列以与父节点交换值的子节点为头的堆。

这个函数可以用于构建大顶堆(compare函数返回>0)或者小顶堆(compare函数返回<0)。在实现时,需要注意PTR_MATH宏的定义,它用于根据baseoffsetwidth计算偏移量。

//堆排序
void heapSort(void* base, int len,int width, int(*compare)(const void* e1, const void* e2)){
   if(base==NULL||compare == NULL||width<=0||len<=1)
        return;
    
    //申请一段用于交换的临时空间
    void* temp = malloc(width);
    if(temp == NULL)
        return;
    
    //从最底部非叶子节点处开始构建堆
    for(int i=len/2-1;i>=0;i--)
        heapify(base,i,len,temp,width,compare);
    
    //循环交换堆顶元素和堆尾元素
    for(int i=0;i<len-1;i++){
        lSwap(PTR_MATH(base,0,width),PTR_MATH(base,len-i-1,width),temp,width);

        //调整堆:将堆的大小减一,然后从堆顶开始进行堆化
        if(len-i>1)
            heapify(base,0,len-i-1,temp,width,compare);
        
    }

    free(temp);
    
}
  1. 初始化堆:从第一个非叶子节点开始,从下往上、从左往右遍历,对每个节点进行堆化操作。堆化操作是将当前节点与其子节点的较大值(或较小值)进行交换,然后继续对当前节点的子节点进行堆化操作。

  2. 交换堆顶元素和堆尾元素:将堆顶元素与堆尾元素交换,使得堆顶元素是待排序序列中的最大值(或最小值)。

  3. 调整堆:将堆的大小减一,然后从堆顶开始,对每个节点进行堆化操作。

  4. 重复步骤2和3,直到堆的大小为1。

        函数heapSort接受四个参数:base(指向数组的指针),len(数组长度),width(数组中每个元素的大小),以及compare(一个比较函数,用于比较两个元素的大小)。

总结

  1. 堆排序算法是一种原地排序算法,不需要额外的存储空间。

  2. 堆排序算法的时间复杂度为O(nlogn),空间复杂度为O(1)。

  3. 堆排序算法在处理部分有序的序列时,效率较高。

  • 28
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值