二叉堆的构建、插入、删除等操作

完全二叉树

若设二叉树的深度为h,除第h层外,其它各层 (1 ~ h-1) 的结点数都达到最大个数,第h层的所有节点都集中在左边的若干位置,这就是完全二叉树




二叉树和二叉堆

  • 二叉树一般采用链表的形式实现
  • 二叉堆一般采用数组的形式实现



二叉堆

  • 具有完全二叉树的特性
  • 二叉堆的根节点称为堆顶
  • 堆中任何一个父节点的值都 >=/<= 它的左右孩子节点
    最大堆:父节点的值 >= 左右孩子节点的值
    在这里插入图片描述
    最小堆
    在这里插入图片描述



以小根堆举例

构建二叉堆:

  • 即一个有 n 个节点的 无序的完全二叉树,把它构建成二叉堆
  • 非叶子节点–从下往上-依次下沉。从下面的非叶子节点开始,让所有非叶子节点依次下沉(从下往上开始)
    在这里插入图片描述



插入节点:

具有完全二叉树的特性,插入一个节点的时候,需要保证节点插入后,仍然是一颗完全二叉树,然后在进行调整,使它满足二叉堆的另一个特性

  • 插入。把新节点插入到完全二叉树的最后一个位置
  • 调整–上浮。把新插入的节点与它的父节点进行比较,如果新节点小于父节点的值,则让新节点上浮,即和父节点交换位置




删除节点(一般删除根节点):

具有完全二叉树的特性,删除一个几点的时候,要马上恢复它具有完全二叉树特性

  • 删除。把根节点删除
  • 最后一个元素顶替删除的根节点为止。用二叉堆最后一个元素顶替上来
  • 调整----顶替后的根节点下沉。把顶替上来的根节点和它的左右孩子比较,如果根节点大于子节点,则让根节点下沉,和子节点交换位置
  • 如何选取子节点?因为是最小堆,要保证父节点比子节点小,因此选择左右节点中小的元素进行交换




举个例子:
数组形式:1 2 3 5 4 6 7 8 9
二叉堆形式:
1
2 3
5 4 6 7
8 9

此处 n 是数组下标
节点位置:n
左孩子位置:2 * n + 1
右孩子位置:2 * n + 2

举例:
节点[2]下标:1
节点[5]下标:3 = 2 * 1 + 1
节点[4]下标:4 = 2 * 1 + 2




看一个代码更加了解一下:

#include <stdio.h>
#include <stdlib.h>
#include <iostream>

using namespace std;
// 下沉:即把当前节点和孩子节点进行比较
void DownAdjust(int * a , int parent , int indexSize)
{
    int temp = a[parent];
    // 左孩子节点先赋值
    int child = 2 * parent + 1;                 // 找到左右节点中的最小值
    
    // 下沉要一直下沉,直到叶子节点
    while(child < indexSize)
    {   
        // 如果存在右节点,找出左右节点中最小的那个进行交换
        if ((child + 1 < indexSize) && (a[child] > a[child + 1]))
        {
            child = child + 1;
        }

        // 当父节点比子节点都小或者相等时,下沉结束
        if(temp <= a[child])
        {
            break;
        }

        // 单向赋值(为啥不是)
        a[parent] = a[child];
        parent = child;
        child = 2 * parent + 1;
    }
    
    a[parent] = temp;
}


// 上浮:对插入的节点执行上浮操作  indexSize插入前最后一个元素的下标
void UpAdjust(int * a , int indexSize)
{
    // 插入的节点的下标
    int chlid = indexSize + 1;
    int temp = a[chlid];

    int parent = (chlid - 1)/2;                 // 为什么要 (child - 1)/2,可以去试试,插入的节点是左节点或者右节点通过-1/2计算出来的值都一样
    while(chlid > 0 && temp < a[parent])        // 即浮到最顶上了
    {
        // 单向赋值。不用每次都交换,因为你发现交换后还得给temp重新赋值,还是之前那个值,直接用temp值去比较就行了
        a[chlid] = a[parent];
        chlid = parent;
        parent = (chlid - 1)/2;
    }
    
    a[chlid] = temp;
}


// 构建二叉堆
void BuildHeap(int * arr , int indexSize)
{
    for(int i = (indexSize - 2)/2 ; i >= 0 ;--i)
    {
        DownAdjust(arr , i , indexSize);
    }
}



void PrintArr(int * a , int size)
{
    for(int i = 0 ; i < 10; ++i)
    {
        printf("%d " , a[i]);
    }
    printf("\n");
}

int main()
{
    int a[10] = {10 , 12, 7 , 8 , 6 ,3 ,5 , 2 ,1 , 9};
    PrintArr(a , 10);
	
	// 本例都是以小根堆为例子
	// 构建二叉堆:即从下往上对非叶子节点进行下沉,每次都沉到底/或父节点比左右子节点都小为止
    BuildHeap(a , 9);

    PrintArr(a , 10);

    return 0;
}

输出结果:

10 12 7 8 6 3 5 2 1 9 
1 2 3 8 6 7 5 12 10 9 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章