目录
一、堆的插入
以小堆为例:
因为过程中需要很多交换 ,所以可以直接写一个交换函数:
void Swap(HPDataType* p1, HPDataType* p2)
{
HPDataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
AdjustUp函数代码如下:
void AdjustUp(HPDataType* a, size_t child)
{
assert(a);
size_t parent = (child - 1) / 2;
while (child > 0)
{
//如果孩子比父亲小则交换
if (a[parent] > a[child])
{
Swap(&a[parent], &a[child]);
//继续
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
向上调整函数写好了就要写插入函数了
因为这里写的堆是用动态数组实现的,所以首先需要判断数组的容量,是否需要扩容。
而整体思想就是将数据给给php->a,且php->size++.
插入完再调用向上调整函数即可。
void HeapPush(Heap* php, HPDataType x)
{
assert(php);
if (php->_size == php->_capacity)
{
//判断容量
int newcapacity = php->_capacity == 0 ? 4: php->_capacity * 2;
HPDataType* tmp = realloc(php->_a, sizeof(HPDataType) * newcapacity);
if (tmp == NULL)
{
printf("realloc failed");
exit(-1);
}
php->_a = tmp;
php->_capacity = newcapacity;
}
php->_a = x;
php->_size++;
AdjustUp(php->_a,php->_size-1);
}
二、堆的删除
以小堆为例:
逻辑上我们看见的堆是二叉树,然而实际的物理结构其实是连续的数组存放。
很多人都会想直接删除根节点,然后后面的数据往前覆盖就好,但是真的可以吗?如图:
再看逻辑图,此时堆的结构全乱了,如图中34为parent却比28child大,所以不再是小堆了,也不是堆。所以这样简单的想当然绝对不可行!!!
为了使堆的结构不被破坏,我们想到将根节点和最后一个叶子节点先交换,而且我们想,删除是不是真的删掉了,我们知道这里构建的堆是数组实现,那么可以直接size--就意味着删掉了一个数据。而且已经交换了,所以这里size--删掉的就是原来想要删除的根节点。
完成交换和删除后,除了现在的根节点,两边保持小堆结构,所以只需要调整根节点。而这里是向下调整,所以命名为AdjustDown
AdjustDown函数代码如下:
void AdjustDown(HPDataType* a, size_t size, size_t root)
{
assert(a);
size_t parent = root;
size_t child = parent * 2 + 1;
while (child<size)
{
//找小孩子的下标
if (child + 1< size && a[child + 1] < a[child])
{
child++;
}
//如果孩子小于父亲就交换
if (a[parent] > a[child])
{
Swap(&a[parent], &a[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
所以删除函数如下:
void HeapPop(Heap* hp)
{
assert(hp);
//根节点和最后一个叶子节点交换
Swap(&hp->_a[0], &hp->_a[hp->_size - 1]);
hp->_size--;
AdjustDown(hp->_a, hp->_size, 0);
}
三、对数组进行排序
这里我运用堆排序,但是每次进行堆排序都不可能写一个堆,所以
第一步、用本来的数组构建堆
这里有两种方式。
第一种:
void HeapSort(int* a, int n)
{
//构建小堆
//1、向上调整构建
for (int i = 1; i < n; i++)
{
AdjustUp(a, i);
}
}
运行结果为小堆:
第二种:
void HeapSort(int* a, int n)
{
//构建堆
//2、向下调整构建
for (int i = (n - 1 - 1)/2 ; i >= 0; i--)
{
AdjustDown(a, n, i);
}
}
运行结果如图:
可以清楚的看出两种方式尽管都是构建的小堆,但是小堆却不一样。
第二步、排序
void HeapSort(int* a, int n)
{
//构建堆
//1、向上调整构建
/*for (int i = 1; i < n; i++)
{
AdjustUp(a, i);
}*/
//2、向下调整构建
for (int i = (n - 1-1)/2; i >= 0; i--)
{
AdjustDown(a, n, i);
}
//降序
int end = n - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);
AdjustDown(a, end, 0);
end--;
}
}
运行结果如下: