对堆的深入理解

堆是一种基本的数据结构。在这里我用数组来形容,在一个二叉堆的数组中,每一个元素都要保证大于等于另外两个特定位置的元素。同时相应的,这些元素又要大于等于另外两个相应位置的元素,整个数据结构以此类推。如果我们将整个数据结构画成树状结构,就能够清晰地看出整个结构的样子。 
对堆的深入理解_第1张图片

开始认为堆上层元素是必须全部大于下层元素的,后来发现这是不正确的

当一颗二叉树的每个结点都大于等于它的两个子节点时,它被称为堆有序。相应地,在堆有序的二叉树中,每个结点都小于等于它的父节点,从任意结点往上,我们都能得到一列非递减的元素;从任意结点往下,我们都能得到一列非递增的元素。根节点就是堆有序的二叉树中的最大节点。所以说在一个堆有序的二叉树中,父节点的位置是K/2,它的两个子节点的位置是2k,2k+1。这样通过索引的变化,我们就可以在数组中得到不同的元素了。

下面是堆的创建插入删除代码

代码每次插入删除都会进行打印,方面新学习堆的同学理解

 

#include 
#include 
typedef struct HeapStruct *MaxHeap ;
struct HeapStruct
{
    int *Element ; // 存储堆元素的数组
    int Size; // 堆的当前元素个数
    int Capacity ; // 堆的最大容量
};

MaxHeap Create ( int MaxSize )
{
    // 创建容量为MaxSize的空的最大堆
    MaxHeap H = malloc ( sizeof ( struct HeapStruct ) ) ;
    H -> Element = malloc ( ( MaxSize + 1 ) * sizeof ( int ) ) ;
// 下标为1的位置开始存值,下标为0的位置不存放堆元素
    H -> Size = 0 ;
    H -> Capacity = MaxSize ;
    H -> Element [0] = 1000 ; // 定义“哨兵”为大于堆中所有可能元素的值 便于以后更快操作
    return H ;
}

void Insert ( MaxHeap H,int item )
{
    // 将元素item插入最大堆H,其中H -> Elements [0] 已经定义为哨兵
    int i ;
    if ( H->Size ==1000 )
    {
        printf ( "最大堆已满" );
        return ;
    }
    i = ++H -> Size ; // i 指向插入后堆中的最后一个元素的位置
    for ( ; H -> Element [ i/2 ] < item; i /= 2 ) // 与父结点做比较,i / 2 表示的就是父结点的下标
        H -> Element [ i ] = H -> Element [ i/2 ]; // 向下过滤结点
    H -> Element [ i ] = item ; //若for循环完成后,i更新为父节点i,然后将 item 插入
    for(i = 1 ; i <= H->Size; i++)
    {
        printf("%d", H->Element[i]);
    }
    printf("\n");
}

int DeleteMax( MaxHeap H )
{
    /* 从最大堆H中取出键值为最大的元素,并删除一个结点 */
    int Parent, Child;
    int MaxItem, temp;
    if (H->Size == 0)
    {
        printf("最大堆已为空");
        return;
    }
    MaxItem = H->Element[1]; /* 取出根结点最大值 */
    /* 用最大堆中最后一个元素从根结点开始向上过滤下层结点 */
    temp = H->Element[H->Size--];
    for( Parent=1; Parent*2<=H->Size; Parent=Child )
    {
        Child = Parent * 2;//child指向根下层元素的左节点
        if( (Child!= H->Size) &&
                (H->Element[Child] < H->Element[Child+1]) )
            Child++; //Child指向左右子结点的较大者
        if( temp >= H->Element[Child] ) break;//如果比较大的节点元素还大,就可以将temp元素放在树根
        else //移动temp元素到下一层
            H->Element[Parent] = H->Element[Child];//将儿子元素移到树根,temp再和下层元素作比较
    }
    H->Element[Parent] = temp;
    int i;
    for(i = 1 ; i <= H->Size; i++)
    {
        printf("%d", H->Element[i]);
    }
    printf("\n");
    return MaxItem;
}
int main()
{
    int l, n, i;

    while(scanf("%d", &n)!=EOF)
    {
        struct HeapStruct *H = Create(n);
        for( i =  0 ; i< n ;i++)
        {
            scanf("%d",&l);
            Insert(H,l);
        }
        printf("请输入需要删除最大值的次数:\n");
        int m;
        scanf("%d", &m);
        while(m--)
        DeleteMax(H);
    }
    return 0;
}

下面是部分代码的讲解

  • 插入代码
void Insert ( MaxHeap H,int item )
{
    // 将元素item插入最大堆H,其中H -> Elements [0] 已经定义为哨兵
    int i ;
    if ( H->Size ==1000 )
    {
        printf ( "最大堆已满" );
        return ;
    }
    i = ++H -> Size ; // i 指向插入后堆中的最后一个元素的位置
    for ( ; H -> Element [ i/2 ] < item; i /= 2 ) // 与父结点做比较,i / 2 表示的就是父结点的下标
        H -> Element [ i ] = H -> Element [ i/2 ]; // 向下过滤结点
    H -> Element [ i ] = item ; //若for循环完成后,i更新为父节点i,然后将 item 插入
    for(i = 1 ; i <= H->Size; i++)
    {
        printf("%d", H->Element[i]);
    }
    printf("\n");
}

如图我们建立一个堆,准备向里面插入数字6

对堆的深入理解_第2张图片

根据代码可知让i = H->size++,并且将6赋值给item进行下面的比较

对堆的深入理解_第3张图片

第一次i = 5, 那么i/2取整得2, 即6与父节点H -> Element [ 2 ] (4)进行比较,因为6>4,所以将H -> Element [ 2 ] (4)的值赋给H -> Element [ 5 ] ,即H -> Element [ i ] = H -> Element [ i/2 ]; //i=5,步骤完成后i = i / 2,将i进行更新,完成后如下图

对堆的深入理解_第4张图片

此时i = 2,那么i/2 = 1,且item (6) > H->Element[1] (5),故重复上述步骤,步骤完成后如下图

对堆的深入理解_第5张图片

现在i  = 1了,但是程序并没有停止,此时i / 2 = 0,这时会和谁进行 比较呢,还记得我们定义了一个哨兵吗,哨兵定义为1000,故比item大,所以程序循环到这里停止,然后H->Element[1] = item,得出最终结果

对堆的深入理解_第6张图片 

你可能感兴趣的:(对堆的深入理解)