一个冷门的排序算法——平滑排序
HangingMirror · · 算法·理论
这个算法比耐心排序还冷门,我也费了很大的劲才学会它。它就是平滑排序(Smooth Sort)。
平滑排序算法是堆排序的变体,由 Edsger Wybe Dijkstra 于1981年提出。它是一种不稳定的比较排序算法,主要用于对大量数据进行排序。它是堆排序的变种,具有较高的效率,并且其最优时间复杂度为
平滑排序算法的基本思想
平滑排序算法基于一个概念叫做“平滑堆”。它使用一个特定的堆结构,该结构使得不需要让元素下沉时几乎一定会到堆的最后面。
平滑排序会建多个按后序遍历记录的堆,每个堆的大小、遍历方法都是确定的。其中,最后一个堆的堆顶最大,倒数第二个堆的堆顶第二大,以此类推。
要弄明白这个结构,首先看齐这个数列(莱恩昂多数列):
比如,前几项莱恩昂多数列是:
这个数列与建堆有很大的联系。如果一个平滑堆有
假如我们已经算出了莱恩昂多数列的前
那么,如果给
建好多个堆之后,最大的数已经在最右边,那么把最大的数当成一个独立的数,不再是最后一个堆的堆顶,此时那个堆就被切分成两个堆,现在场上的堆就多一个。
然后移动堆顶的元素,使它们重新有序。(注意,只将堆顶的元素排序,而就只有最后两个数无序,所以用插入排序,在插入时先比较这个堆的两个孩子,选出较大的,再与下一个堆的堆顶比较,若更小则继续传送元素,否则留下它,并不断将它与较大的孩子交换)排序后,次大的数和次次大的数又已经在最右边。
重复这两个操作,直到所有的数归位。
这种堆结构保证了排序操作的高效性。
平滑排序算法的美中不足
平滑排序使用了不够平衡的结构,常数大,且最坏情况常数很大,实现也不易。这就是为什么我不给出平滑排序的代码。
如果你们一定要平滑排序的代码的话,我可以提供一个非原创的代码。它的逻辑可能和我讲的不太一样。
注意,这个非原创的代码和 stdc++game.h 一样只是一个头文件,不能单独运行。
无注释版本:
#ifndef Smoothsort_Included
#define Smoothsort_Included
#include <iterator>
#include <algorithm>
#include <bitset>
template <typename RandomIterator>
void Smoothsort(RandomIterator begin, RandomIterator end);
template <typename RandomIterator, typename Comparator>
void Smoothsort(RandomIterator begin, RandomIterator end, Comparator comp);
namespace smoothsort_detail {
const size_t kNumLeonardoNumbers = 46;
const size_t kLeonardoNumbers[kNumLeonardoNumbers] = {
1u, 1u, 3u, 5u, 9u, 15u, 25u, 41u, 67u, 109u, 177u, 287u, 465u, 753u,
1219u, 1973u, 3193u, 5167u, 8361u, 13529u, 21891u, 35421u, 57313u, 92735u,
150049u, 242785u, 392835u, 635621u, 1028457u, 1664079u, 2692537u,
4356617u, 7049155u, 11405773u, 18454929u, 29860703u, 48315633u, 78176337u,
126491971u, 204668309u, 331160281u, 535828591u, 866988873u, 1402817465u,
2269806339u, 3672623805u
};
struct HeapShape {
std::bitset<kNumLeonardoNumbers> trees;
size_t smallestTreeSize;
};
template <typename RandomIterator>
RandomIterator SecondChild(RandomIterator root) {
return root - 1;
}
template <typename RandomIterator>
RandomIterator FirstChild(RandomIterator root, size_t size) {
return SecondChild(root) - kLeonardoNumbers[size - 2];
}
template <typename RandomIterator, typename Comparator>
RandomIterator LargerChild(RandomIterator root, size_t size, Comparator comp) {
RandomIterator first = FirstChild(root, size);
RandomIterator second = SecondChild(root);
return comp(*first, *second)? second : first;
}
template <typename RandomIterator, typename Comparator>
void RebalanceSingleHeap(RandomIterator root, size_t size, Comparator comp) {
while (size > 1) {
RandomIterator first = FirstChild(root, size);
RandomIterator second = SecondChild(root);
RandomIterator largerChild;
size_t childSize;
if (comp(*first, *second)) {
largerChild = second;
childSize = size - 2; // ... and has order k - 2.
} else {
largerChild = first; // First child is larger...
childSize = size - 1; // ... and has order k - 1.
}
if (!comp(*root, *largerChild))
return;
std::iter_swap(root, largerChild);
root = largerChild;
size = childSize;
}
}
template <typename RandomIterator, typename Comparator>
void LeonardoHeapRectify(RandomIterator begin, RandomIterator end,
HeapShape shape, Comparator comp) {
RandomIterator itr = end - 1;
size_t lastHeapSize;
while (true) {
lastHeapSize = shape.smallestTreeSize;
if (size_t(std::distance(begin, itr)) == kLeonardoNumbers[lastHeapSize] - 1)
break;
RandomIterator toCompare = itr;
if (shape.smallestTreeSize > 1) {
RandomIterator largeChild = LargerChild(itr, shape.smallestTreeSize,
comp);
if (comp(*toCompare, *largeChild))
toCompare = largeChild;
}
RandomIterator priorHeap = itr - kLeonardoNumbers[lastHeapSize];
if (!comp(*toCompare, *priorHeap))
break;
std::iter_swap(itr, priorHeap);
itr = priorHeap;
do {
shape.trees >>= 1;
++shape.smallestTreeSize;
} while (!shape.trees[0]);
}
RebalanceSingleHeap(itr, lastHeapSize, comp);
}
template <typename RandomIterator, typename Comparator>
void LeonardoHeapAdd(RandomIterator begin, RandomIterator end,
RandomIterator heapEnd,
HeapShape& shape, Comparator comp) {
if (!shape.trees[0]) {
shape.trees[0] = true;
shape.smallestTreeSize = 1;
}
else if (shape.trees[1] && shape.trees[0]) {
shape.trees >>= 2;
shape.trees[0] = true;
shape.smallestTreeSize += 2;
}
else if (shape.smallestTreeSize == 1) {
shape.trees <<= 1;
shape.smallestTreeSize = 0;
shape.trees[0] = true;
}
else {
shape.trees <<= shape.smallestTreeSize - 1;
shape.trees[0] = true;
shape.smallestTreeSize = 1;
}
bool isLast = false;
switch (shape.smallestTreeSize) {
case 0:
if (end + 1 == heapEnd)
isLast = true;
break;
case 1:
if (end + 1 == heapEnd || (end + 2 == heapEnd && !shape.trees[1]))
isLast = true;
break;
default:
if (size_t(std::distance(end + 1, heapEnd)) < kLeonardoNumbers[shape.smallestTreeSize - 1] + 1)
isLast = true;
break;
}
if (!isLast)
RebalanceSingleHeap(end, shape.smallestTreeSize, comp);
else
LeonardoHeapRectify(begin, end + 1, shape, comp);
}
template <typename RandomIterator, typename Comparator>
void LeonardoHeapRemove(RandomIterator begin, RandomIterator end,
HeapShape& shape, Comparator comp) {
if (shape.smallestTreeSize <= 1) {
do {
shape.trees >>= 1;
++shape.smallestTreeSize;
} while (shape.trees.any() && !shape.trees[0]);
return;
}
const size_t heapOrder = shape.smallestTreeSize;
shape.trees[0] = false;
shape.trees <<= 2;
shape.trees[1] = shape.trees[0] = true;
shape.smallestTreeSize -= 2;
RandomIterator leftHeap = FirstChild(end - 1, heapOrder);
RandomIterator rightHeap = SecondChild(end - 1);
HeapShape allButLast = shape;
++allButLast.smallestTreeSize;
allButLast.trees >>= 1;
LeonardoHeapRectify(begin, leftHeap + 1, allButLast, comp);
LeonardoHeapRectify(begin, rightHeap + 1, shape, comp);
}
}
template <typename RandomIterator, typename Comparator>
void Smoothsort(RandomIterator begin, RandomIterator end, Comparator comp) {
if (begin == end || begin + 1 == end) return;
smoothsort_detail::HeapShape shape;
shape.smallestTreeSize = 0;
for (RandomIterator itr = begin; itr != end; ++itr)
smoothsort_detail::LeonardoHeapAdd(begin, itr, end, shape, comp);
for (RandomIterator itr = end; itr != begin; --itr)
smoothsort_detail::LeonardoHeapRemove(begin, itr, shape, comp);
}
template <typename RandomIterator>
void Smoothsort(RandomIterator begin, RandomIterator end) {
Smoothsort(begin, end,
std::less<typename std::iterator_traits<RandomIterator>::value_type>());
}
#endif
相关链接
杨树排序算法
受平滑排序启发的一种更简单的算法是杨树排序。它以荷兰圩田中常见的一排排大小递减的树命名,对于大部分未排序的输入,它更快,但最好情况的时间复杂度退化成了
杨树分类的重大变化在于,堆顶没有保持排序顺序。所以,每次在找最大数时,都会
作者还建议使用完美的二叉树而不是莱恩昂多树来进一步简化,但作用并不显著。
结语
有没有人发现了一个问题?
假如我们已经算出了莱恩昂多数列的前
i 项,那么一个大小为f(i) ,开头为j 的平滑堆不需要额外的空间,就可以实现O(1) 找儿子。找右儿子可直接访问a(j+f(i)-2) ,找左儿子可直接访问a(j+f(i-1)-1) 。
也就是说,给定
这个请大家自己讨论,把想法留在评论区,答案令人震惊。
感谢浏览。