前言
这次我们介绍堆排序。堆是一种完全二叉树的数据结构。
他的父节点总会大于他的子节点叫做“大堆”。
他的父节点总会小于他的子节点叫做“小堆”。
虽然画的很丑,但是结构大体如图这样的。
思路
我们用数组来保存二叉树。每个父节点的左子节点的下标,是父节点下标的两倍加一,右子节点的的下标,是父节点下标的两倍加二。 我们之前在介绍二叉树时有介绍这样的方法。
所以我们先构建一个用来创建堆的结构体:
typedef struct Heap
{
DATATYPE* _val;
DATATYPE _size;
DATATYPE _capacity;
}Heap;
这个结构体里包含元素集合, 目前元素个数,和最大容量三个变量。
当我们要构建一个小堆时,则需要满足,父节点始终小于子节点。在排序时,我们首先找到两个子节点中较小的那一个。然后与父节点比较,如果这个子节点比父节点小的话交换两个节点的位置。 如果完成交换。就对交换后的父节点和新的子节点重复这个过程,直到满足小堆的要求。
构建大堆也是这样的道理。
显而易见,使用堆排序可以更加快速的完成排序。因为我们不需要去依次遍历所有的数据。只需要向下遍历二叉树的深度就可以完成这个元素集合的处理。时间复杂度为O(nlogn)。
但是缺点也很明显。就是我们并没有对所有数据都完整的排序。也就意味着我们并不能一次性输出一个有序的完整元素集合。
代码实现
我们完成一趟排序,其中 n代表元素集合个数, root是我们开始向下调整的位置。
void AdJustDown(DATATYPE* data, int n, int root) //向下调整 小堆
{
int parent = root;
int child = parent * 2 + 1;
while (child < n)
{
if (child + 1 >= n);
else if (data[child + 1] < data[child])
{
child += 1;
}
if (data[child] < data[parent])
{
Swap(&data[child], &data[parent]);
parent = child;
child = parent * 2 + 1;
}
else
break;
}
}
在对所有的数据排序时,我们需要从后往前排。否则构建出来的堆是错误的,因为我们在排根节点时,需要保证他的左右子树皆为小堆(大堆),在调整根节点后才能保证依旧是小堆(大堆)。
Heap* HeapInit(DATATYPE* data, int n)
{
Heap* heap = (Heap*)malloc( sizeof(Heap));
heap->_val = (DATATYPE*)malloc(sizeof(DATATYPE) * n);
heap->_capacity = heap->_size = n;
for (int i = (n - 1 - 1) / 2; i >= 0; --i)
AdJustDown(heap->_val,heap->_size,i);
return heap;
}
当然我们还会有弹出,或者插入一个新的数据的操作。
DATATYPE HeapPop(Heap* heap)
{
DATATYPE tmp = heap->_val[0];
Swap(&(heap->_val[0]), &(heap->_val[heap->_size - 1]));
heap->_size--;
AdJustDown(heap->_val, heap->_size, 0);
return tmp;
}
void HeapPush(Heap* heap, DATATYPE val)
{
if (heap->_capacity == heap->_size)
{
heap->_capacity *= 2;
heap->_val = (DATATYPE*)realloc(heap->_val, heap->_capacity * sizeof(DATATYPE));
}
heap->_size++;
heap->_val[heap->_size-1] = val;
AdJustUp(heap->_val, heap->_size, heap->_size - 1);
}
值得注意的是,当我们在使用堆排序时,一般都是在处理前几名,或者需要找到特定排名的问题时,在弹出操作时,我们弹出了堆顶,即根节点的值,在插入时,我们从构成二叉树的数组的末尾插入。然后在对末尾新插入的向上调整到合适的位置就好。
void AdJustUp(DATATYPE* data,int n, int child) // 末尾向上调整
{
int parent = (child-1)/2;
while (parent >= 0)
{
if (data[parent] > data[child])
{
Swap(&data[parent], &data[child]);
child = parent;
parent = (child - 1) / 2;
}
else
break;
}
}