STL之partial_sort算法源码讲解
假设有一个容器,它保存了 100 万个数值,但我们只对其中最小的 100 个感兴趣。可以对容器的全部内容排序,然后选择前 100 个元素,但这可能有点消耗时间。这时候需要使用部分排序partial_sort,只需要这些数中的前100个是有序放置的。目录:1. partial_sort 接口说明2. partial_sort 用法举例3. partial_sort 原理概述4. partial_s.
假设有一个容器,保存了 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() 后后面元素的顺序是不确定的,这取决于具体的实现。
也可以指定不同的比较函数。例如:
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的原理:
- 对原始容器内区间为[first, middle)的元素执行
make_heap()
操作构造一个大顶堆, - 然后遍历剩余区间[middle, last)中的元素,剩余区间的每个元素均与大顶堆的堆顶元素进行比较(大顶堆的堆顶元素为最大元素,该元素为第一个元素,很容易获得),若堆顶元素较小,则交换堆顶元素和遍历得到的元素值(
pop_heap
),并重新调整该大顶堆以维持该堆为大顶堆(adjust_heap
)。 - 遍历结束后,[first, middle)区间内的元素便是排名在前的m个元素,再对该堆做一次堆排序
sort_heap()
便可得到最后的结果。
下图为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源码剖析》 6.7.8 partial_sort, P386.
- STL之partial_sort排序学习: https://blog.csdn.net/jfkidear/article/details/8922974
- C++ partial_sort(STL partial_sort)排序算法详解: http://c.biancheng.net/view/564.html
- 无聊写排序之 ----部分排序(Partial Sort): https://blog.csdn.net/dreamhougf/article/details/47106849
- 【STL】算法 — partial_sort: https://blog.csdn.net/nestler/article/details/25882261
更多推荐
所有评论(0)