heap排序算法


前言

堆数据结构可以有两种表示方法

  1. 用指针表示
    在这里插入图片描述

  2. 用一维数组表示
    在这里插入图片描述
    堆是完全二叉树,索引从0开始,满足下述条件
    若父节点索引为 i ,则它的左孩子节点索引为 2i+1 ,右孩子节点索引为 2i + 2
    若孩子节点(包括左右孩子)索引为 i ,则其父节点为 (i-1)/2 向下取整


push_heap

template<class RandomAccessIterator>
inline void push_heap(RandomAccessIterator first, 
                      RandomAccessIterator last)
{
    // 此函数调用时,新元素已置于底部容器的最尾端
    __push_heap_aux(first, last, distance_type(first), value_type(first));
}
template<class RandomAccessIterator, class Distance, class T>
inline void __push_heap_aux(RandomAccessIterator first, 
                            RandomAccessIterator last, Distance*, T*)
{
    __push_heap(first, Distance((last - first) - 1), Distance(0), T(*(last - 1)));
    //以上系根据implicit representation heap的结构特性:新值必须置于底部
    //容器的最尾端,此即第一个洞号:(last-first)-1
}
template<class RandomAccessIterator, class Distance, class T>
void __push_heap(RandomAccessIterator first, Distance holeIndex,
                 Distance topIndex, T value)
{
    Distance parent = (holeIndex - 1) / 2;  // 找出父节点
    while (holeIndex > topIndex && *(first + parent) < value){
        // 当尚未到达顶端,且父节点小于新值,STL heap是一种max-heap
        *(first + holeIndex) = *(first + parent);  //令洞值为父值
        holeIndex = parent;             // percolate up:调整洞号,向上提升至父节点
        parent = (holeIndex - 1) / 2;   //新洞的父节点
    } //持续至顶端,或满足heap的次序特性
    *(first + holeIndex) = value; //令子节点值为新值,完成插入操作
}

pop_heap

将heap中最后一个元素暂存到value中,将堆顶元素“弹出”到最后,此时[first, last-1)为没有堆顶的堆,通过__adjust_heap将堆[first, last-1)调整,最后剩下个holeIndex被放入value,然后[top, holeIndex]就变成了除了holeIndex元素外其它的满足堆特性,所以用__push_heap调整之,至此,pop_heap完成

//将堆顶元素弹出(放入容器末位)
template<class RandomAccessIterator>
inline void pop_heap(RandomAccessIterator first, RandomAccessIterator last)
{
    __pop_heap_aux(first, last, value_type(first));
}
template<class RandomAccessIterator, class T>
inline void __pop_heap_aux(RandomAccessIterator first,
                           RandomAccessIterator last, T*)
{
    __pop_heap(first, last-1, last-1, T(*(last-1)), distance_type(first));
}
template<class RandomAccessIterator, class T, class Distance>
inline void __pop_heap(RandomAccessIterator first,
                       RandomAccessIterator last,
                       RandomAccessIterator result,
                       T value, Distance*)
{   
    // 容器的最后一个元素的值被覆盖,它的值现存在value里
    *result = *first;
    __adjust_heap(first, Distance(0), Distance(last - first), value);
}
template<class RandomAccessIterator, class Distance, class T>
void __adjust_heap(RandomAccessIterator first, Distance holeIndex,
                   Distance len, T value)
{
    // value 存储容器中最后一个元素值(改写之前)
    Distance topIndex = holeIndex;
    Distance secondChild = 2 * holeIndex + 2; // 右孩子,最大为last-first-1
    // 右孩子小于区间长度[first, last)  [0,... ,last-first-1]
    // 在堆中
    while (secondChild < len){
        //比较洞(父)节点左右两个子值,然后以secondChild代表较大子节点
        if (*(first+secondChild) < *(first+secondChild-1)){
            --secondChild;
        }
        // 令较大的子节点值为洞值,再令洞号下移至较大子结点处
        *(first + holeIndex) = *(first + secondChild);
        holeIndex = secondChild;
        // 找出新洞的右孩子
        secondChild = 2 * secondChild + 2;
    }
    // index 最大值为len-1
    if (secondChild == len){ //没有右子节点,只有左子节点
        // percolate down
        // 此时holeIndex已经等于secondChild
        *(first + holeIndex) = *(first + (secondChild - 1));
        holeIndex = secondChild - 1;
    }
    // 下面的侯捷之见不对,不能改,必须用__push_heap, 
    // 
    //将想要调整值填入目前的洞号内,此时肯定满足次序特性? 
    // 依侯捷之见 *(first + holeIndex) = value; 应该也可
    __push_heap(first, holeIndex, topIndex, value);
}

make_heap

// make_heap,将[first, last)排列为一个heap
template<class RandomAccessIterator>
inline void make_heap(RandomAccessIterator first, RandomAccessIterator last)
{
    __make_heap(first, last, value_type(first), distance_type(first));
}

template<class RandomAccessIterator, class T, class Distance>
void __make_heap(RandomAccessIterator first, RandomAccessIterator last,
                 T*, Distance*)
{
    if (last - first < 2) return ; //如果长度为0,1不必排列
    Distance len = last - first;
    // 找出第一个需要重排的子树头部,以parent标示出。由于任何叶子节点都不需执行
    // perlocate down,所以有以下计算。parent命名不佳,名为holeIndex更好
    // len-1最大索引值, (i-1)/2得到父节点
    Distance parent = ( len - 1 -1 ) / 2;
    while (true){
        // 重排以parent为首得子树。len是为了让__adjust_heap()判断操作范围
        __adjust_heap(first, parent, len, T(*(first + parent)));
        if (parent == 0){
            return ;       // 走完根节点就结束
        }
        --parent;          //  即将重排之子树得头部向前一个节点
    } 
}

sort_heap

// sort_heap算法,前提必须符合heap条件
template<class RandomAccessIterator>
void sort_heap(RandomAccessIterator first,
               RandomAccessIterator last)
{
    // 每执行一次pop_heap(),极值(在STL heap中为极大)即被放在尾端
    // 扣除尾端再执行一次pop_heap(),次极值又被放在尾端,如此排序
    while (last - first > 1){
        pop_heap(first, last--);
    }
}

总结

  1. 堆是完全二叉树,可以用指针或者数组表示。
  2. push_heap的前提是最后一个元素之前满足堆条件,push_heap将最有一个元素插入堆中,插入后整个数组又都满足堆条件。
  3. pop_heap的前提是整个数组必须满足堆条件,pop_heap将堆顶元素即数组第一个元素弄到数组最后一个元素的位置(最后一个元素暂存起来),然后将剩余元素调整成堆。
  4. heap可用于解决 Top N 问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值