大堆与小堆
二者都是完全二叉树。
大堆:父节点的值总是大于或等于子节点
大堆:父节点的值总是小于或等于子节点
int child = parent * 2 + 1; (父节点与子节点下标关系)
堆的逻辑结构和物理结构的关联
物理结构是一个数组,而逻辑结构是一个完全二叉树。
堆排序的思路(分两步)
例子如下:现有数组a[]={1,9,3,4,8,3},要将其升序排列。
第一步
第一步:要求升序,则将其原堆建为大堆
若要求降序,则将其原堆建为小堆
(原因在最后会讲)
如何将原堆建为大堆?
用大白话讲就是:
先找到最后一层最右边的那一个叶子节点a,再找a的父节点b,从b开始进行向下调整操作,然后遍历数组,重复向下调整操作(下标减小,直到<0结束)。
向下调整操作的代码:
void AdjustDown(int* a, int n, int parent) // n是 要调整的堆 的 最后一个数 的下标+1
{ //parent就是图中的父节点的下标
int child = parent * 2 + 1;
while (child < n)
{
// 选出左右孩子中小/大的那个(假设法)
if (child + 1 < n
&& a[child + 1] > a[child]) //child + 1 < n确保有右孩子才进行比较,防越界
{
++child; //此时孩子为右孩子
}
if (a[child] > a[parent])
{
Swap(&a[parent], &a[child]); //父子交换
parent = child;
child = parent * 2 + 1; //父子向下更替
}
else
{
break;
}
}
}
纵观全过程,每一个进行向下调整操作的数,其左子树和右子树都是大堆,其实这就是向下调整操作能进行的前提条件。
第二步
第二步:堆顶与最后一个数进行交换,将最后一个数不看做堆里的数(堆里的数减少一个),在堆顶处进行向下调整操作。重复操作,直到堆里没有数。此刻,整个数组已有序,堆排序完成。
第一步的若要求升序,则将其原堆建为大堆
若要求降序,则将其原堆建为小堆 在这里就明白了:
因为堆顶与最后一位进行了交换,那么最大值其实在最后一位。
堆排序的代码(升序)
#include<stdio.h>
void Swap(int* p1, int* p2) //交换函数
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void AdjustDown(int* a, int n, int parent) // n是 要调整的堆 的 最后一个数 的下标+1
{ //parent就是图中的父节点的下标
int child = parent * 2 + 1;
while (child < n)
{
// 选出左右孩子中小/大的那个(假设法)
if (child + 1 < n
&& a[child + 1] > a[child]) //child + 1 < n确保有右孩子才进行比较,防越界
{
++child; //此时孩子为右孩子
}
if (a[child] > a[parent])
{
Swap(&a[parent], &a[child]); //父子交换
parent = child;
child = parent * 2 + 1; //父子向下更替
}
else
{
break;
}
}
}
void HeapSort(int* a, int n)
{
// 升序 -- 建大堆
// 降序 -- 建小堆
// 建堆--向下调整建堆 --O(N)
for (int i = (n - 1 - 1) / 2; i >= 0; --i) //(n - 1 - 1) / 2:文中所指的b点的下标 遍历的过程
{
AdjustDown(a, n, i);
}
int end = n - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);
// 再调整,选出次大的数
AdjustDown(a, end, 0); // end是 “要调整的堆” 的 最后一个数 的下标+1
--end; // “要调整的堆” 的 最后一个数一直在变,因为堆中的数在变少
}
}
int main()
{
int a[] = {1 ,9 ,3 ,4 ,8 ,3};
int n = sizeof(a) / sizeof(int);
HeapSort(a, n);
for (int i = 0; i < n; i++)
{
printf("%d ", a[i]);
}
return 0;
}
执行结果为:
到这里就结束了,有什么问题和错误,欢迎指出来,博主画图不易,希望得到你的关注和点赞哦!