堆的构建, 以及堆排序的c++实现

    堆是一种数据结构,就是每个节点根据某种规则排序, 从根节点往下都符合某种规律,

根节点的值比所有节点的值都大, 称为最大堆;

根节点的值比所有节点的值都小, 称为最小堆;

为了统一操作, 我们规定:

1. 堆存储在 顺序数组中, 下标从 1 开始,

2. 以大根堆为例 

3. 元素个数为 n 或者 len

堆排序 step:

        一)建树部分

                    1.   找到一个树的最后一个非叶节点, 计算公式为 [n/2] 向下取整, 然后遍历树的每个非叶节点,使其符合堆的规则

                                                 

红圈为最后一个 非叶 子节点

 void make_heap(int *a, int len)
 {
    for(int i =  len/2; i >= 1; --i)    //遍历每个 非叶子节点
         adjust_heap(a, i, len);//不用考虑那么多, 用面向对象的思想去考虑,   
 }                     //这个函数的作用就是用来使 当前节点的子树   符合   堆的规律

                    2. 要使某节点的当前节点的字数符合 堆规律,需要以下操作:

void adjust_heap(int* a, int node, int len)
{
    int left = 2 * node;
    int right = 2 * node + 1;
    int max = node;
    if( left <= len && a[left] > a[max]) //left <= len 防止节点不存在
        max = left;
    if( right <= len && a[right] > a[max]) //a[right] > a[max] 判断是不是根节点最大(因为你每个子树都要符合大根堆的性质啊) 
        max = right;
    if(max != node)//确实最大值不是 父节点
    {
        swap( a[max], a[node]);  //交换节点
        adjust_heap(a, max, len);//从当前节点开始,调整下面的堆
    }//关于这里递归的解释请看下面
}

90 和 7交换后, 发现 7 不能管住 原来90的两个儿子, 那怎么办呢??那就继续调用adjust() 让他符合

所以需要递归。

到此为止

这个整个树都符合 堆的规律了, 最大堆就已经建造好了。

二)排序部分

此时 [1, len] 为一个堆 , 现在我们需要把 整个堆中的元素 排 升序, 该如何呢  ???            

1) 将堆的顶部,与最后一个元素交换。

         此时 最后一个元素已经有序, 且是整个个序列中的最大值, 这符合特性

2) 剩下元素 [1, len-1] 所组成的树已经不是 堆了。(因为此时顶部的元素是最小的)。   

         所以, 要将剩下的元素通过 adjust函数调整成 堆。

         然后,继续将剩余元素中的最后一个元素 与 新堆的顶部交换 。。。。。。重复 1) 2)

代码如下:

    for(int i = len; i >= 1; --i)//2. 堆排序
    {
        swap(a[1], a[i]);
        adjust_heap(a, 1, i - 1);//剩余堆的范围变成了 [1, len-1]
    } //由上一步的交换可能破坏堆的性质, 故要对剩余的堆进行调整

完整代码如下:

#include <iostream>
using namespace std;
//为了统一操作, 堆存储在 顺序数组中, 下标从 1 开始, 以大根堆为例

void adjust_heap(int* a, int father, int len)//判断是不是符合大根堆
{
    int left = 2 * father;
    int right = 2 * father + 1;
    int max = father;
    if( left <= len && a[left] > a[max])//left <= len 防止节点不存在
        max = left;
    if( right <= len && a[right] > a[max])//a[right] > a[max] 判断是不是根节点最大(因为你每个子树都要符合大根堆的性质啊)
        max = right;
    if(max != father)
    {
        swap( a[max], a[father]);//交换节点
        adjust_heap(a, max, len);//从当交换后前节点开始, 调整下面的堆
    }//这里的 max 是当前 father 的某一个儿子, 而不是 father 自身, 因为交换后 father 本来就是有序的.
}

void heap_sort(int* a, int len)
{
    for(int i = len / 2; i >= 1; --i) //1.创建堆make_heap, 从最后一个非叶子节点开始
        adjust_heap(a, i, len);

    for(int i = len; i >= 1; --i)//2. 堆排序, 此时 [1, len] 为一个堆
    {
        swap(a[1], a[i]);      //将 堆顶元素(数组首位) 与 堆末元素(数组末位) 互换.
        adjust_heap(a, 1, i - 1); // 此时堆末元素已经有序(存的是最大值), 
    }// 剩余堆的范围变成了 [1, len-1], 但由于上一步的交换可能破坏堆的性质, 故要进行调整
}

int main()
{
    int a[11] = {-999, 7, 3, 4, 5, 2, 0, -1, 10, 0, 9};
    int len = sizeof(a) / sizeof(int);
    //cout << len << endl;

    for(int i = 1; i <= len; ++i)
        cout << a[i] << ' ';
    cout << endl;

    heap_sort(a, len);//len = 11, 但是约定 a[0] 不存值, 有意义的值从 a[1] 开始

    for(int i = 1; i <= len; ++i)
        cout << a[i] << ' ';
    cout << endl;

    return 0;
}

            然而堆排序中的几个函数,还是可以通过优化来提升 时间复杂度的。比如 adjust()函数的优化

如果在用 adjust在调整最大堆时, 交换需要以下操作:

            temp  = a[node];

            a[node] = a[max];

            a[max] = temp;

需要操作三次,100000次 的交换需要操作 300000次。太过于耗时。所以我们可以做类似于插入排序的优化。

        if( 节点的某一个儿子 > 节点本身 )

                  用 temp 把儿子存起来,  并把节点赋值给儿子;

    用temp上浮一层到节点的位置, 继续执行判断, 这样一层层不停的上浮。直到不符合条件

        if( temp < 同一层的兄弟节点 || temp < 父亲节点)

                   把 temp 赋值给当前层的元素;

       这样省去了每次的交换, 100000的操作只需要操作100000次。大大提高了效率

  • 41
    点赞
  • 158
    收藏
    觉得还不错? 一键收藏
  • 17
    评论
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值