数据结构与算法----堆

251 阅读3分钟

堆的特性

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;
}

注意:这个转化过程后面的堆排序也会用到