[data structure] heap 堆

  • 定义

1  (二叉) 堆是一颗完全二叉树。

2   任何一个节点都小于它的后裔节点。(最小堆),相应的,最大堆中的任何一个节点大于它的后裔节点。


由于二叉堆是一个完全二叉树,因此对于二叉堆中的任何一个节点i,它的子节点的编号为:

A[ left ]  =  A[ 2*i + 1 ]     ;      A[ right ] = A [ 2*i + 2 ]   (注:这是与实际实现时相对应的,比如如下的堆:)

由于堆的完全二叉树的性质,我们使用数组来表示堆即可。如下图所示:

     

左图是一个大顶堆,堆顶的元素是最大的。下方的数组是与该堆对应的。(注:上述的数组的有序是纯属偶然的。)


首要的,堆的操作最重要的性质便是保持堆序性。也就是说我们在加入元素以及删除元素(一般是删除堆顶)之后,需要对堆作出调整,使得堆依旧满足堆序性。


  • 插入元素

向堆中加入新的元素i,输入:要添加的元素elem。使得加入后依然是堆。简单的示意图如下:

                 插入元素15,那么需要判断15是否破坏了堆序性,即是否该元素放在了正确的位置,显然 15 > 10,不满足堆序性,因此15上滤,得到右图,由于 15 < 18,说明15到达了正确的位置,因此成功插入了元素15.

  • 保持堆序性:

这是堆的所有操作的最重要的一个函数:通过递归的比较当前元素与它的左右子节点,决定是否将该元素下滤,直到到达正确的位置。

C++代码如下:

//max heapify for the i-th elem in A
void max_heapify(int A[], int  i, int heap_size)
{
    int largest = i;
    int left = 2*i+1;
    int right = 2*i+2;
    if(left < heap_size && A[left] > A[largest])
    {
        largest = left;
    }
    if(right < heap_size && A[right] > A[largest])
    {
        largest = right;
    }
    if(largest != i)
    {
        swap(A[i],A[largest]);
        max_heapify(A,largest,heap_size);
    }
}


  • 建堆

给定数组A,要求将A变成一个堆,也就是说逐渐改变数组A中的元素位置,使得数组保持堆序性。

假设给定数组A   =  {  3   13    2    8   18   10 },要将该数组构建成一个最大堆。

首先,在构建最大堆之前,必须明确:

1    由完全二叉树的性质,数组的至少一半的元素为叶子节点:

一个简单的例子:对于一颗整的完全二叉树而言,第 i  层的元素个数为 2^(i-1),(根节点为第0层!!注意与平时树的高度的区别)。那么有如下示意图:


因此,前i层的节点总数为:

1 + 2 + 2^2 + ... + 2^(i-1) = 2^i - 1

第i+1层(即叶子节点层)的节点个数为2^i ,因此叶子节点的个数大约为总节点个数的一半。

另一方面想,由于完全二叉树中父节点与子节点之间的位置关系,也可以知道对于某个节点i而言,如果它的右孩子编号大于整个数组的大小,那么显然节点i是最后一个中间节点,它后面的节点应该全部为叶子节点。即

2*i+2 =  len, 那么 i = ( len - 2 ) / 2。因此基于此我们建堆的过程只需要考虑数组中的前一半的元素,代码如下:

void build_max_heap(int A[], int len)
{
    for(int i = len/2 ; i >= 0 ; i--)
    {
        max_heapify(A,i,len);
    }
}

  • 堆排序:

由于每一次从堆中找出最大的元素(大顶堆)是很直接的,直接取出对顶元素即可,那么在删除堆顶的元素之后:

1    堆中的元素个数会减一

2    堆顶空缺,需要将剩下的最大的元素放置在堆顶

如下的简单示意图:





按照上述一直到堆中的元素为0,那么数组中的元素也就有序了(从小到大有序)。

代码如下:

void heapSort(int A[], int len)
{
    build_max_heap(A,len);
    //printHeap(A,len);
    for(int i = len-1; i >= 0 ; --i)
    {
        swap(A[i],A[0]);
        max_heapify(A,0,i);
    }
}

因此,堆排序实现完毕。

注:如果你是要将数组从大到小的排序,那么你应该建立的是小顶堆,否则为大顶堆。

堆的各种操作大致都是堆的高度O(logn)量级的。

总结:堆是很重要的数据结构,在任务优先级调度方面可作为优先级队列来使用,每次选择优先级最高的任务。

堆的操作还有很多,比如改变某个关键字的值,或者是删除中间的某个关键字。值得一提的是,堆排序是一种in-place排序,复杂度为O(nlogn)。


补充:由于在实现堆的时候,老是要去画二叉树,于是自己实现了一个简陋的只针对于完全二叉树的程序:

static void printSpace(int number)
{
    while(number--)cout << " ";
}
void printHeap(int A[], int len)
{
    if(len == 0 )
    {
        cout << "len == 0 !!!" << endl;
        return;
    }
    int level = log10(len)/log10(2)+1;//得到树的高度
    cout << "level = " << level << endl;
    printSpace(level*2);
    cout << A[0];
    printSpace(level*2);
    level--;
    cout << endl;
    int index = 1;
    for(int i = 0;i < level; ++i )
    {
        int count = pow(2,i);
        while(count--)
        {
            printSpace((level-i)*2);
            if(index < len) cout << A[index++];
            printSpace((level-i)*2);
            if(index < len)cout << A[index++];
            printSpace(count);
        }
        cout << endl;
    }
}

PS:简陋的程序,只能用于画完全二叉树。


  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据提供的引用内容,我们可以看出这是一个关于堆的实现和应用的问题。引用\[1\]提到了一种使用顺序表存储的方式来实现堆,但是这种方式存在空间浪费的问题。引用\[2\]列举了堆的接口函数和堆排序的过程。引用\[3\]介绍了一种常用且优化的表示方法,即左孩子右兄弟表示法。 根据问题描述,警告C6386是指在写入"popk"时发生了缓冲区溢出。根据提供的代码,问题出现在源文件的第64行。具体原因可能是在该行代码中,将数据写入了名为"popk"的缓冲区,但是该缓冲区的大小不足以容纳写入的数据,导致溢出。 为了解决这个问题,我们需要检查源文件中的相关代码,确保在写入缓冲区时不会超出其大小限制。可能需要调整缓冲区的大小或者使用更安全的写入方式来避免缓冲区溢出的问题。 #### 引用[.reference_title] - *1* *3* [二叉树第一弹之树和堆的概念和结构、基础堆接口函数的实现(编写思路加逻辑分析加代码实操,一应俱全的汇总...](https://blog.csdn.net/AMor_05/article/details/127175020)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [第九章:C语言数据结构与算法初阶之堆](https://blog.csdn.net/yanyongfu523/article/details/129582526)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值