美文网首页
STL之partial_sort算法源码讲解

STL之partial_sort算法源码讲解

作者: AlbertGou | 来源:发表于2019-03-26 19:25 被阅读0次

本文首发于个人CSDN博客: https://blog.csdn.net/ggq89/article/details/88817085

假设有一个容器,保存了 100 万个数值,但我们只对其中最小的 100 个元素的顺序感兴趣。可以对容器的全部内容排序,然后选择前 100 个元素,但这样处理会使效率低。这时候需要使用部分排序 partial_sort,高效的获得这些数中的前100个最小数的顺序。

1. partial_sort 接口说明……

partial_sort有两个版本,一个默认以小于(less-than操作符)作为比较规则,出来的顺序为递增排列。另一个可以传入一个比较函数,即调用者指定一个比较规则。

partial_sort(beg,mid,end)
partial_sort(beg,mid,end,comp)

对容器中的(mid-beg)个元素进行排序,partial_sort 完成之后,从beg到mid(但不包括mid)范围内的元素时有序的,且大于(或者指定的comp函数)mid之后的元素。未排序元素之间的次序是不确定的,即partial_port不是稳定排序算法。

2. partial_sort 用法举例

下面这段代码演示了 partial_sort() 算法的工作方式:

size_t count {5}; // Number of elements to be sorted
std::vector<int> numbers {22, 7, 93, 45, 19, 56, 88, 12, 8, 7, 15, 10};
std::partial_sort(std::begin(numbers), std::begin(numbers) + count, std::end(numbers));

执行partial__sort()后的效果如下图所示:

partial_sort() 算法的操作

需要注意的是,不能保持未排序元素的原始顺序。在执行 partial_sort() 后后面元素的顺序是不确定的,这取决于具体的实现。

也可以指定不同的比较函数。例如:

std::partial_sort(std::begin(numbers), std::begin(numbers) + count, std:: end (numbers) , std::greater<>());

现在,number 中最大的 count 个元素会是容器开始处的一个降序序列。以上语句的输出结果为:

93 88 56 45 22 7 19 12 8 7 15 10

同样的,不能保持 numbers 中未排序元素的原始顺序。

3. partial_sort 原理概述

那么 partial_sort 的原理是什么呢?是堆排序!

partial_sort的原理:

  1. 对原始容器内区间为[first, middle)的元素执行 make_heap() 操作构造一个大顶堆,
  2. 然后遍历剩余区间[middle, last)中的元素,剩余区间的每个元素均与大顶堆的堆顶元素进行比较(大顶堆的堆顶元素为最大元素,该元素为第一个元素,很容易获得),若堆顶元素较小,则交换堆顶元素和遍历得到的元素值(pop_heap ),并重新调整该大顶堆以维持该堆为大顶堆(adjust_heap)。
  3. 遍历结束后,[first, middle)区间内的元素便是排名在前的m个元素,再对该堆做一次堆排序 sort_heap() 便可得到最后的结果。

下图为partial_sort 算法的步骤详解:

partial_sort() 算法步骤详解

4. partial_sort 源码讲解

下面是STL中 partial_sort 的源码讲解,考虑到篇幅,这里只列出第一个版本的。

// 版本一
template <class RandomAccessIterator>
inline void partial_sort(RandomAccessIterator first,
    RandomAccessIterator middle,
    RandomAccessIterator last) {
        __partial_sort(first, middle, last, value_type(first));
}
 
template <class RandomAccessIterator, class T>
void __partial_sort(RandomAccessIterator first, RandomAccessIterator middle,
    RandomAccessIterator last, T*) {
        make_heap(first, middle); //将区间[first, middle)构造为一个堆结构
        for (RandomAccessIterator i = middle; i < last; ++i)
            if (*i < *first)    // 遍历堆以外的元素,并将更优的元素放入堆中
                __pop_heap(first, middle, i, T(*i), distance_type(first)); // first值放i中,i的原值融入heap并调整
        sort_heap(first, middle); // 对最终的堆进行排序
}

先来分析 make_heap 算法, 该算法将一段指定的数据排列出max-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标记。由于任何叶子节点都不需要处理。
        // parent 命名不佳, 为 holeIndex 更好
        Distance parent = (len - 2)/2; 
 
        while (true) {
            // 重排以parent为首的子树,以len为操作范围
            __adjust_heap(first, parent, len, T(*(first + parent)));
            if (parent == 0) return; // 走完根节点,结束
            parent--; // 向前排列前一个节点
        }
}

下面来重点分析 __adjust_heap 算法, 该算法调整从first开始的len个元素,洞号为holeIndex,洞值为value, 最终获得一个 max-heap

template <class RandomAccessIterator, class Distance, class T>
void __adjust_heap(RandomAccessIterator first, Distance holeIndex,
    Distance len, T value) {
        Distance topIndex = holeIndex;
        Distance secondChild = 2 * holeIndex + 2; // holeIndex的右子节点
        while (secondChild < len) {
            // 比较holeIndex两个子节点的值,用secondChild代表值较大的子节点
            if (*(first + secondChild) < *(first + (secondChild - 1)))
                secondChild--;   
            // 令较大子值为洞值,注意:原洞值已在函数形参value中得以保存
            *(first + holeIndex) = *(first + secondChild); 
            // 再让洞号下移到左子节点处,
            holeIndex = secondChild;
            // 算出新的洞节点的右子节点
            secondChild = 2 * (secondChild + 1);
        }
        // 如果没有右子节点,只有左子节点
        if (secondChild == len) { 
            // 令左子节点为洞值,然后将洞号下移到左子节点
            *(first + holeIndex) = *(first + (secondChild - 1));
            holeIndex = secondChild - 1;
        }
        // 将原洞值push到新的洞号中。
        // 以下语句的效果类似于: *(first + holeIndex) = value; 
        __push_heap(first, holeIndex, topIndex, value);
}

进一步讲解__adjust_heap 算法 调用的 __push_heap 算法,该算实现将新元素value push到max-heap中topIndex到holeIndex的合适位置中,其中max-heap的起始位置是first。

template <class RandomAccessIterator, class Distance, class T>
void __push_heap(RandomAccessIterator first, Distance holeIndex,
    Distance topIndex, T value) {
        Distance parent = (holeIndex - 1) / 2;  // 找到父节点
        // 当尚未达到顶端, 且父节点的值小于新值(不符合max-heap的次序特性)
        while (holeIndex > topIndex && *(first + parent) < value) {
            *(first + holeIndex) = *(first + parent); // 移动父值到洞号处
            holeIndex = parent; // 调整洞号为父节点
            parent = (holeIndex - 1) / 2; // 新洞的父节点
        }  // 循环到顶端,或者满足max-heap的顺序为止
        *(first + holeIndex) = value; // 将新值放入循环完得到的洞号,完成push操作
}

再回头看 __partial_sort 算法,当*i < *first时,代码中没有互换i所指元素和first所指元素。实际上这一步操作是在 __pop_heap 函数 完成的,在该函数内,先将要first元素放入指定的result,然后用新值value去调整max-heap。

template <class RandomAccessIterator, class T, class Distance>
inline void __pop_heap(RandomAccessIterator first, RandomAccessIterator last,
                       RandomAccessIterator result, T value, Distance*) {
  *result = *first; // 将heap顶端元素值放入 result中
  // 重新调整heap,洞号为0,欲调整的新值为value
  __adjust_heap(first, Distance(0), Distance(last - first), value);
}

最后来看看 sort_heap 算法,该算法将[first, last)中的元素由堆序变为增序排列。每次弹出堆的最大值并放入尾部,然后缩小堆的范围,循环执行弹出操作直至堆只剩下最后一个元素。


template <class RandomAccessIterator>
void sort_heap(RandomAccessIterator first, RandomAccessIterator last) {
    while (last - first > 1)  // 直到只剩一个元素为止
        pop_heap(first, last--); // 每执行一次,范围缩小一格
}

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*) {
        // 将first元素值(即最大值)放入last-1,然后重调[first, last-1)为max-heap
        __pop_heap(first, last-1, last-1, T(*(last-1)), distance_type(first));
}

参考文章:

相关文章

网友评论

      本文标题:STL之partial_sort算法源码讲解

      本文链接:https://www.haomeiwen.com/subject/iiinvqtx.html