介绍
堆排序是一种基于比较的排序算法,它利用堆这种数据结构进行排序。堆是一种特殊的完全二叉树,它的每个节点都大于等于(或小于等于)其子节点。堆排序的主要思想是将待排序的序列构造成一个大顶堆(或小顶堆),然后将堆顶元素与堆尾元素交换,将堆的大小减一,再调整堆,重复这个过程直到堆的大小为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。
实现原理:
-
首先,检查width是否小于等于0,如果是,则直接返回,不执行任何操作。
-
如果temp为空,则分配一个宽度为width的临时内存块,并将地址赋值给temp_。如果temp不为空,则将temp的值赋给temp_。
-
使用memcpy函数将e1中的数据复制到temp_中,然后将e2中的数据复制到e1中,最后将temp_中的数据复制到e2中。
-
如果temp为空,则释放分配的临时内存块。
用途:
这个函数可以用于交换两个结构体、数组或其他数据类型的内存块。例如,在实现一个链表交换节点数据的函数时,可以使用这个函数来交换两个节点的数据。
注意事项:
-
确保传入的指针e1和e2是有效的,否则可能导致程序崩溃。
-
确保传入的width是一个正整数,否则可能导致程序无法正确执行。
-
如果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
:比较回调函数,用于比较两个元素的大小。
函数的主要逻辑如下:
- 初始化堆头
dad
为begin
,第一个子节点son
为dad*2+1
。 - 使用一个
while
循环,不断地将当前堆排序完成,直到子节点son
超过堆的结束位置end
。 - 在循环中,首先检查子节点
son
是否存在,如果不存在,则跳出循环。 - 然后,判断当前父节点
dad
是否有两个子节点,如果有,则根据要构建大顶堆还是小顶堆取响应值。 - 如果相应值大于或小于父节点
dad
,则交换值。 - 当前堆排序完成,排列以与父节点交换值的子节点为头的堆。
这个函数可以用于构建大顶堆(compare
函数返回>0
)或者小顶堆(compare
函数返回<0
)。在实现时,需要注意PTR_MATH
宏的定义,它用于根据base
、offset
和width
计算偏移量。
//堆排序
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);
}
-
初始化堆:从第一个非叶子节点开始,从下往上、从左往右遍历,对每个节点进行堆化操作。堆化操作是将当前节点与其子节点的较大值(或较小值)进行交换,然后继续对当前节点的子节点进行堆化操作。
-
交换堆顶元素和堆尾元素:将堆顶元素与堆尾元素交换,使得堆顶元素是待排序序列中的最大值(或最小值)。
-
调整堆:将堆的大小减一,然后从堆顶开始,对每个节点进行堆化操作。
-
重复步骤2和3,直到堆的大小为1。
函数heapSort接受四个参数:base
(指向数组的指针),len
(数组长度),width
(数组中每个元素的大小),以及compare
(一个比较函数,用于比较两个元素的大小)。
总结
-
堆排序算法是一种原地排序算法,不需要额外的存储空间。
-
堆排序算法的时间复杂度为O(nlogn),空间复杂度为O(1)。
-
堆排序算法在处理部分有序的序列时,效率较高。