堆的特性:
1.堆分为大头堆和小头堆,因此,堆中某个节点的值总是不大于或不小于其父节点的值
2.堆总是一棵完全二叉树
从上可以推出,堆的最大节点(大头堆)或者最小节点(小偷对)尽可能靠拢根节;,另一个最值节点,则不确定性的在某个叶子节点下
大头堆、小头堆的示例图如下:
堆的生成过程
以大头堆为例:
已知一个完全二叉树,内部是数字顺序为乱序结构,那么如果想给此完全二叉树转化成大头堆,需要经过如下步骤:
1.自下而上,从有子叶子节点开始向上遍历
2.如果为叶子节点,结束本函数,回到上个函数栈
3.如果为父节点,查看左右节点是否有比自己大的,如果存在,把最大的节点数值与自己替换,其父子关系不能随便改变(避免递归时,额外向上多走一步)
4.一直替换到根节点结束
由于是自下而上处理,所有的子堆都处理成大头堆,那么整个堆则为大头堆
小头堆与大头堆的转化逻辑相似,只不过找最小的到最上面罢了
代码实现逻辑如下:
//定义一个树结构
typedef struct Tree {
int data;
struct Tree *lChild, *rChild;
} LSTree;
typedef struct Queue {
LSTree *data[MAXSIZE];
int front, rear;
} LSOrderQueue;
//生成堆
void generateHeap(LSTree *node) {
if (!node->lChild && !node->rChild) return;
//向下查找
if (node->lChild) generateHeap(node->lChild);
if (node->rChild) generateHeap(node->rChild);
//进行对比替换,maxNode 为最大子节点
LSTree *maxNode = node->lChild;
if (!maxNode) {
maxNode = node->rChild;
}else if (node->rChild) {
maxNode = maxNode->data > node->rChild->data ? maxNode : node->rChild;
}
//交换节点值
if (node->data < maxNode->data) {
int data = node->data;
node->data = maxNode->data;
maxNode->data = data;
}
}
完全二叉树生成堆的逻辑就是这样了,然而实际使用中,线性数组作为完全二叉树生成堆是占多数的,例如堆排序
线性数组作为完全二叉树生成堆
这里将线性数组作为完全二叉树,生成堆,参考二叉树的储存一章,一个父节点的子节点索引最小为2n+1,所以length/2以下才会有子节点(上一排总是下一排的一半,完全二叉树的叶子节点总要比上面的父节点加起来都多),所以堆从父节点开始调整,所以才要从 length/2才需要转化成堆,能减少叶子节点们的无效遍历
线性数组完全二叉树转化成堆,经过以下步骤:
1.循环,从第一个有子节点的节点开始调整堆
2.找到当前节点的第一个子节点(左节点)sub,索引为start * 2 + 1
3.如果右节点存在,则对比右节点是否比自己大,如果比自己大,则把右节点的值赋值给sub
4.对比最大子节点是否比当前节点(其父节点大),如果小于父节点,则结束,否则交换数值
5.回到步骤1,继续下一轮,知道索引降低为1,即:到达根节点
//给定一个数组,生成堆
void sortByHeap(int list[], int length) {
for (int i = length / 2; i >= 0; i--) { //length / 2因为完全二叉树子节点索引最小为2n+1,所以最小也得length/2才可能会有子节点
heapShift(list, i, length -1);
}
}
//将子堆转化大头堆的逻辑
void heapShift(int list[], int start, int end) {
int ori = list[start]; //保存默认根节点的值
int sub = start * 2 + 1; //表示的是根节点的第一个子节点
//子节点索引要在需调整范围内
while (sub <= end) {
//右节点也不能大于索引,并且选出最小的一个和根节点比较
if (sub < end && list[sub] < list[sub + 1]) sub++;
if (ori >= list[sub]) break; //不比根节点小,结束
list[start] = list[sub];
start = sub; //更新,用于同步更新根节点,以便于保持堆结构
sub = sub * 2 + 1;
}
list[start] = ori;
}
注意:这个转化过程后面的堆排序也会用到