一文读懂特殊的树结构——堆

专栏目录(数据结构与算法解析):https://blog.csdn.net/qq_40344524/article/details/107785323

堆(完全二叉树)

定义

堆是计算机科学中一类特殊的数据结构的统称。n个元素的序列{k1,k2,ki,…,kn}当且仅当满足(ki <= k2i,ki <= k2i+1)或者(ki >= k2i,ki >= k2i+1), (i = 1,2,3,4…n/2)时,称之为堆。若将和此次序列对应的一维数组(即以一维数组作此序列的存储结构)看成是一个完全二叉树,则堆的含义表明,完全二叉树中所有非终端结点的值均不大于(或不小于)其左、右孩子结点的值。由此,若序列{k1,k2,…,kn}是堆,则堆顶元素(或完全二叉树的根)必为序列中n个元素的最小值(或最大值)。

堆通常是一个可以被看做一棵树(完全二叉树)的数组对象。根据结点特征可以将堆分为大顶堆和小顶堆两种,将根节点最大的堆叫做最大堆或大顶堆,根节点最小的堆叫做最小堆或小顶堆。

性质

1.堆中某个节点的值总是不大于或不小于其父节点的值。
2.堆总是一棵完全二叉树。

堆的结构

typedef struct HNode * Heap; //堆的类型定义
struct HNode{
	ElementType *Data; //存储元素的数组 
	int Size;          //堆中当前元素的个数 
	int Capacity;	   //堆的最大容量 
};
typedef Heap MaxHeap;  //最大堆 
typedef Heap MinHeap;  //最小堆 

基本操作

1、堆的构造
2、堆的初始化
3、插入节点
4、删除节点
5、堆排序

代码实现

接下来通过C语言实现堆的基本操作,以下讲解均以原始数据为a[]={4,1,3,2,16,9.10.14.8.7}为基础,实现的操作在最大堆上进行。

1、堆的构造

数据结构如下:

typedef struct HNode * Heap; //堆的类型定义
struct HNode{
	ElementType *Data; //存储元素的数组 
	int Size;          //堆中当前元素的个数 
	int Capacity;	   //堆的最大容量 
};
typedef Heap MaxHeap;  //最大堆 

2、堆的初始化

基本思想:首先将每个叶子结点视为一个堆,再将每个叶子结点于其父节点一起构成一个包含更多结点的堆。所以在构造堆的时候,首先需要找到最后一个结点的父节点,从这个节点开始构造最大堆,直到该节点前面的所有分支节点都处理完毕,具体过程可用如下代码表示。

void MaxHeapInit(MaxHeap &H)
{
    for(int i=H.HeapSize/2;i>=1;i--)
    {
        H.heap[0]=H.heap[i];
        int son=i*2;
        while(son<H.HeapSize)
        {
            if(son<H.HeapSize&&H.heap[son]<H.heap[son+1])
                son++;
            if(H.heap[i]>H.heap[son])
                break;
            else if(son<H.heapSize&&H.heap[son]>H.heap[son+1]
                {
                    H.heap[son/2]=H.heap[son];
                    son*=2;
                }
        }
        H.heap[son/2]=H.heap[0];
    }
}

注意: 在二叉树中,若当前节点的下标为 i, 则其父节点的下标为 i/2,其左子节点的下标为 i2,其右子节点的下标为i2+1;

3、插入节点

最大堆中插入节点,先在堆末尾插入该节点,然后按照堆的初始化过程将该节点放入到合适的位置。

void MaxHeapInsert(MaxHeap &H, EType &x)
{
    if(H.HeapSize==H.MaxSize) return false;
    
    int i=++H.HeapSize;
    while(i!=1&&x>H.heap[i/2])
    {
        H.heap[i]=H.heap[i/2];
        i/=2;
    }
    H.heap[i]=x;
    return true;
}

4、删除节点

将最大堆的最后一个节点放到根节点,然后删除最大值,然后再把新的根节点放到合适的位置

void MaxHeapDelete(MaxHeap &H, EType &x)
{
    if(H.HeapSize==0) return false;
    x=H.heap[1];
    H.heap[0]=H.heap[H.HeapSize--];
    int i=1, son=i*2;
     while(son<H.HeapSize)
        {
            if(son<H.HeapSize&&H.heap[son]<H.heap[son+1])
                son++;
            if(H.heap[i]>H.heap[son])
                break;
             H.heao[i]=H.heap[son];
            i=son;
            son*=2;
        }
        H.heap[i]=H.heap[0];
    return true;
    
}

5、堆排序

1、将带排序的序列构造成一个大顶堆,根据大顶堆的性质,当前堆的根节点(堆顶)就是序列中最大的元素;2、将堆顶元素和最后一个元素交换,然后将剩下的节点重新构造成一个大顶堆;3、重复步骤2,如此反复,从第一次构建大顶堆开始,每一次构建,我们都能获得一个序列的最大值,然后把它放到大顶堆的尾部。最后,就得到一个有序的序列了。


#include<iostream>
using namespace std;
 
void swap(int &a, int &b)
{
    int temp=a;
    a=b;
    b=temp;
}
 
void quick_build(int a[], int len, int root)
{
    int left=root*2+1;
    int flag=left;
    while(left<len)
    {
        int right=left+1;
        while(right<len&&a[right]>a[left])
            flag=right;
        
    }
    if(a[root]<a[flag])
    {
        swap(a[root],a[flag]);
        heap_build(a,len,flag);
}
 
void quick_sort(int a[], int len)
{
    for(int i=len/2;i>0;i--)
        heap_build(a,len, i);
    for(int j=len-1;j>0;j--)
    {
        swap(a[0],a[j]);
        heap_build(a,0,j);
    }  
        
}

总结

通过上述学习我们知道堆的本质就是一棵二叉树,由于堆的特殊性质我们可以用它来简化排序算法,而且堆是实现抽象数据类型优先队列的一种方式,优先队列有很广泛的应用,例如Huffman编码中使用优先队列利用贪心算法构建最优前缀编码树。

堆的概念就先讲到这里,下一节我们学习另一个特殊的数据结构——图结构。


以上是我的一些粗浅的见解,有表述不当的地方欢迎指正,谢谢!


表述能力有限,部分内容讲解的不到位,有需要可评论或私信,看到必回…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

书山客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值