我正在解决一个问题,它涉及非常快速地对 10 个数字 (int32) 进行排序。我的应用程序需要尽可能快地对 10 个数字进行数百万次排序。我正在对数十亿个元素的数据集进行采样,每次我需要从中挑选 10 个数字(简化)并对它们进行排序(并从排序的 10 个元素列表中得出结论)。
目前我正在使用 insertion sort,但我想我可以为我的 10 个数字的特定问题实现一个非常快速的自定义排序算法,这将击败插入排序。
我该如何解决这个问题?
if
语句应该是最好的。避免循环。
(按照@HelloWorld 的建议研究排序网络。)
似乎 29 比较/交换网络是进行 10 输入排序的最快方法。我使用 Waksman 在 1969 年发现的网络作为 JavaScript 中的这个示例,它应该直接翻译成 C,因为它只是 if
语句、比较和交换的列表。
function sortNet10(data) { // Waksman 的十输入排序网络,1969 var swap;如果(数据 [0] > 数据 [5]){ 交换 = 数据 [0];数据[0] = 数据[5];数据[5] = 交换; } if (data[1] > data[6]) { swap = data[1];数据[1] = 数据[6];数据[6] = 交换; } if (data[2] > data[7]) { swap = data[2];数据[2] = 数据[7];数据[7] = 交换; } if (data[3] > data[8]) { swap = data[3];数据[3] = 数据[8];数据[8] = 交换; } if (data[4] > data[9]) { swap = data[4];数据[4] = 数据[9];数据[9] = 交换; } if (data[0] > data[3]) { swap = data[0];数据[0] = 数据[3];数据[3] = 交换; } if (data[5] > data[8]) { swap = data[5];数据[5] = 数据[8];数据[8] = 交换; } if (data[1] > data[4]) { swap = data[1];数据[1] = 数据[4];数据[4] = 交换; } if (data[6] > data[9]) { swap = data[6];数据[6] = 数据[9];数据[9] = 交换; } if (data[0] > data[2]) { swap = data[0];数据[0] = 数据[2];数据[2] = 交换; } if (data[3] > data[6]) { swap = data[3];数据[3] = 数据[6];数据[6] = 交换; } if (data[7] > data[9]) { swap = data[7];数据[7] = 数据[9];数据[9] = 交换; } if (data[0] > data[1]) { swap = data[0];数据[0] = 数据[1];数据[1] = 交换; } if (data[2] > data[4]) { swap = data[2];数据[2] = 数据[4];数据[4] = 交换; } if (data[5] > data[7]) { swap = data[5];数据[5] = 数据[7];数据[7] = 交换; } if (data[8] > data[9]) { swap = data[8];数据[8] = 数据[9];数据[9] = 交换; } if (data[1] > data[2]) { swap = data[1];数据[1] = 数据[2];数据[2] = 交换; } if (data[3] > data[5]) { swap = data[3];数据[3] = 数据[5];数据[5] = 交换; } if (data[4] > data[6]) { swap = data[4];数据[4] = 数据[6];数据[6] = 交换; } if (data[7] > data[8]) { swap = data[7];数据[7] = 数据[8];数据[8] = 交换; } if (data[1] > data[3]) { swap = data[1];数据[1] = 数据[3];数据[3] = 交换; } if (data[4] > data[7]) { swap = data[4];数据[4] = 数据[7];数据[7] = 交换; } if (data[2] > data[5]) { swap = data[2];数据[2] = 数据[5];数据[5] = 交换; } if (data[6] > data[8]) { swap = data[6];数据[6] = 数据[8];数据[8] = 交换; } if (data[2] > data[3]) { swap = data[2];数据[2] = 数据[3];数据[3] = 交换; } if (data[4] > data[5]) { swap = data[4];数据[4] = 数据[5];数据[5] = 交换; } if (data[6] > data[7]) { swap = data[6];数据[6] = 数据[7];数据[7] = 交换; } if (data[3] > data[4]) { swap = data[3];数据[3] = 数据[4];数据[4] = 交换; } if (data[5] > data[6]) { swap = data[5];数据[5] = 数据[6];数据[6] = 交换; } 返回(数据); } document.write(sortNet10([5,7,1,8,4,3,6,9,2,0]));
这是网络的图形表示,分为独立的阶段。
https://i.stack.imgur.com/pp7EG.png
为了利用并行处理,可以将 5-4-3-4-4-4-3-2 分组更改为 4-4-4-4-4-4-3-2 分组。
https://i.stack.imgur.com/IcV0u.png
当您处理此固定大小时,请查看 sorting networks。这些算法具有固定的运行时间并且独立于它们的输入。对于您的用例,您没有某些排序算法所具有的开销。
Bitonic sort 是这样一个网络的实现。这个在 CPU 上最适合 len(n) <= 32。在更大的输入上,您可以考虑转移到 GPU。
顺便说一句,比较排序算法的好页面是这里的(虽然它缺少 bitonic sort
):
使用以 4 个为一组进行比较的排序网络,因此您可以在 SIMD 寄存器中进行比较。一对打包的最小/最大值指令实现了打包比较器功能。抱歉,我现在没有时间寻找我记得看到的关于此的页面,但希望在 SIMD 或 SSE 排序网络上搜索会出现一些问题。
x86 SSE 确实具有用于四个 32 位整数向量的压缩 32 位整数最小和最大指令。 AVX2(Haswell 和更高版本)具有相同的但对于 8 个整数的 256b 向量。还有有效的洗牌指令。
如果您有很多独立的小排序,则可以使用向量并行执行 4 或 8 个排序。特别是。如果您随机选择元素(因此要排序的数据无论如何在内存中不会是连续的),您可以避免洗牌并简单地按您需要的顺序进行比较。 10 个寄存器用于保存 4 个(AVX2:8)10 个整数列表中的所有数据,但仍有 6 个寄存器用于暂存空间。
如果您还需要对关联数据进行排序,则向量排序网络的效率会降低。在这种情况下,最有效的方法似乎是使用打包比较来获取更改元素的掩码,并使用该掩码混合(引用)相关数据的向量。
展开的、无分支的选择排序呢?
#include <iostream>
#include <algorithm>
#include <random>
//return the index of the minimum element in array a
int min(const int * const a) {
int m = a[0];
int indx = 0;
#define TEST(i) (m > a[i]) && (m = a[i], indx = i );
//see http://stackoverflow.com/a/7074042/2140449
TEST(1);
TEST(2);
TEST(3);
TEST(4);
TEST(5);
TEST(6);
TEST(7);
TEST(8);
TEST(9);
#undef TEST
return indx;
}
void sort( int * const a ){
int work[10];
int indx;
#define GET(i) indx = min(a); work[i] = a[indx]; a[indx] = 2147483647;
//get the minimum, copy it to work and set it at max_int in a
GET(0);
GET(1);
GET(2);
GET(3);
GET(4);
GET(5);
GET(6);
GET(7);
GET(8);
GET(9);
#undef GET
#define COPY(i) a[i] = work[i];
//copy back to a
COPY(0);
COPY(1);
COPY(2);
COPY(3);
COPY(4);
COPY(5);
COPY(6);
COPY(7);
COPY(8);
COPY(9);
#undef COPY
}
int main() {
//generating and printing a random array
int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
std::random_device rd;
std::mt19937 g(rd());
std::shuffle( a, a+10, g);
for (int i = 0; i < 10; i++) {
std::cout << a[i] << ' ';
}
std::cout << std::endl;
//sorting and printing again
sort(a);
for (int i = 0; i < 10; i++) {
std::cout << a[i] << ' ';
}
return 0;
}
http://coliru.stacked-crooked.com/a/71e18bc4f7fa18c6
唯一相关的行是前两个 #define
。
它使用两个列表并完全重新检查第一个列表十次,这将是一个糟糕的选择排序,但是它避免了分支和可变长度循环,这可以弥补现代处理器和如此小的数据集。
基准
我对排序网络进行了基准测试,我的代码似乎更慢。但是我试图删除展开和副本。运行此代码:
#include <iostream>
#include <algorithm>
#include <random>
#include <chrono>
int min(const int * const a, int i) {
int m = a[i];
int indx = i++;
for ( ; i<10; i++)
//see http://stackoverflow.com/a/7074042/2140449
(m > a[i]) && (m = a[i], indx = i );
return indx;
}
void sort( int * const a ){
for (int i = 0; i<9; i++)
std::swap(a[i], a[min(a,i)]); //search only forward
}
void sortNet10(int * const data) { // ten-input sorting network by Waksman, 1969
int swap;
if (data[0] > data[5]) { swap = data[0]; data[0] = data[5]; data[5] = swap; }
if (data[1] > data[6]) { swap = data[1]; data[1] = data[6]; data[6] = swap; }
if (data[2] > data[7]) { swap = data[2]; data[2] = data[7]; data[7] = swap; }
if (data[3] > data[8]) { swap = data[3]; data[3] = data[8]; data[8] = swap; }
if (data[4] > data[9]) { swap = data[4]; data[4] = data[9]; data[9] = swap; }
if (data[0] > data[3]) { swap = data[0]; data[0] = data[3]; data[3] = swap; }
if (data[5] > data[8]) { swap = data[5]; data[5] = data[8]; data[8] = swap; }
if (data[1] > data[4]) { swap = data[1]; data[1] = data[4]; data[4] = swap; }
if (data[6] > data[9]) { swap = data[6]; data[6] = data[9]; data[9] = swap; }
if (data[0] > data[2]) { swap = data[0]; data[0] = data[2]; data[2] = swap; }
if (data[3] > data[6]) { swap = data[3]; data[3] = data[6]; data[6] = swap; }
if (data[7] > data[9]) { swap = data[7]; data[7] = data[9]; data[9] = swap; }
if (data[0] > data[1]) { swap = data[0]; data[0] = data[1]; data[1] = swap; }
if (data[2] > data[4]) { swap = data[2]; data[2] = data[4]; data[4] = swap; }
if (data[5] > data[7]) { swap = data[5]; data[5] = data[7]; data[7] = swap; }
if (data[8] > data[9]) { swap = data[8]; data[8] = data[9]; data[9] = swap; }
if (data[1] > data[2]) { swap = data[1]; data[1] = data[2]; data[2] = swap; }
if (data[3] > data[5]) { swap = data[3]; data[3] = data[5]; data[5] = swap; }
if (data[4] > data[6]) { swap = data[4]; data[4] = data[6]; data[6] = swap; }
if (data[7] > data[8]) { swap = data[7]; data[7] = data[8]; data[8] = swap; }
if (data[1] > data[3]) { swap = data[1]; data[1] = data[3]; data[3] = swap; }
if (data[4] > data[7]) { swap = data[4]; data[4] = data[7]; data[7] = swap; }
if (data[2] > data[5]) { swap = data[2]; data[2] = data[5]; data[5] = swap; }
if (data[6] > data[8]) { swap = data[6]; data[6] = data[8]; data[8] = swap; }
if (data[2] > data[3]) { swap = data[2]; data[2] = data[3]; data[3] = swap; }
if (data[4] > data[5]) { swap = data[4]; data[4] = data[5]; data[5] = swap; }
if (data[6] > data[7]) { swap = data[6]; data[6] = data[7]; data[7] = swap; }
if (data[3] > data[4]) { swap = data[3]; data[3] = data[4]; data[4] = swap; }
if (data[5] > data[6]) { swap = data[5]; data[5] = data[6]; data[6] = swap; }
}
std::chrono::duration<double> benchmark( void(*func)(int * const), const int seed ) {
std::mt19937 g(seed);
int a[10] = {10,11,12,13,14,15,16,17,18,19};
std::chrono::high_resolution_clock::time_point t1, t2;
t1 = std::chrono::high_resolution_clock::now();
for (long i = 0; i < 1e7; i++) {
std::shuffle( a, a+10, g);
func(a);
}
t2 = std::chrono::high_resolution_clock::now();
return std::chrono::duration_cast<std::chrono::duration<double>>(t2 - t1);
}
int main() {
std::random_device rd;
for (int i = 0; i < 10; i++) {
const int seed = rd();
std::cout << "seed = " << seed << std::endl;
std::cout << "sortNet10: " << benchmark(sortNet10, seed).count() << std::endl;
std::cout << "sort: " << benchmark(sort, seed).count() << std::endl;
}
return 0;
}
与排序网络相比,我一直在无分支选择排序中获得更好的结果。
$ gcc -v
gcc version 5.2.0 (GCC)
$ g++ -std=c++11 -Ofast sort.cpp && ./a.out
seed = -1727396418
sortNet10: 2.24137
sort: 2.21828
seed = 2003959850
sortNet10: 2.23914
sort: 2.21641
seed = 1994540383
sortNet10: 2.23782
sort: 2.21778
seed = 1258259982
sortNet10: 2.25199
sort: 2.21801
seed = 1821086932
sortNet10: 2.25535
sort: 2.2173
seed = 412262735
sortNet10: 2.24489
sort: 2.21776
seed = 1059795817
sortNet10: 2.29226
sort: 2.21777
seed = -188551272
sortNet10: 2.23803
sort: 2.22996
seed = 1043757247
sortNet10: 2.2503
sort: 2.23604
seed = -268332483
sortNet10: 2.24455
sort: 2.24304
for ( ; i<10; i++) (m > a[i]) && (m = a[i], indx = i );
优化得非常好。 (短路通常是一种分支形式)
for (int n = 0; n<10; n++) a[n]=g();
更改 std::shuffle
。执行时间减半,网络速度更快。
std::sort
相比如何?
std::sort
,但它的表现非常糟糕,我什至没有将它包含在基准测试中。我猜想使用很小的数据集会有相当大的开销。
问题并不是说这是某种基于 Web 的应用程序。引起我注意的一件事是:
我正在对数十亿个元素的数据集进行采样,每次我需要从中挑选 10 个数字(简化)并对它们进行排序(并从排序的 10 个元素列表中得出结论)。
作为一名软件和硬件工程师,这绝对让我尖叫FPGA。我不知道您需要从排序后的一组数字中得出什么样的结论或数据来自何处,但我知道处理 1 亿到 10 亿之间的某个位置几乎是微不足道的 这些“排序和分析”操作每秒。我过去做过 FPGA 辅助的 DNA 测序工作。当问题非常适合这种类型的解决方案时,几乎不可能击败 FPGA 的强大处理能力。
在某种程度上,唯一的限制因素是您可以多快将数据铲入 FPGA 以及您可以多快将其取出。
作为参考,我设计了一个高性能实时图像处理器,它以大约每秒 3 亿像素的速率接收 32 位 RGB 图像数据。数据流过 FIR 滤波器、矩阵乘法器、查找表、空间边缘检测块和许多其他操作,然后再从另一端传出。所有这些都在一个相对较小的 Xilinx Virtex2 FPGA 上实现,内部时钟从大约 33 MHz 到(如果我没记错的话)400 MHz。哦,是的,它还有一个 DDR2 控制器实现并运行两组 DDR2 内存。
FPGA 可以在每次时钟转换时输出一种 10 个 32 位数,同时以数百 MHz 运行。随着数据填充处理管道/秒,操作开始时会有短暂的延迟。之后,您应该能够每时钟获得一个结果。如果可以通过复制排序和分析管道来并行化处理,或者更多。原则上,解决方案几乎是微不足道的。
关键是:如果应用程序不受 PC 限制,并且数据流和处理与 FPGA 解决方案“兼容”(无论是独立的还是作为机器中的协处理器卡),那么您就没有办法无论算法如何,都能够使用任何语言编写的软件超越可达到的性能水平。
我刚刚进行了快速搜索,发现了一篇可能对您有用的论文。看起来它可以追溯到 2012 年。今天(甚至在那时)你可以在性能上做得更好。这里是:
我最近编写了 a little class,它使用 Bose-Nelson 算法在编译时生成排序网络。
它可用于为 10 个数字创建非常快速的排序。
/**
* A Functor class to create a sort for fixed sized arrays/containers with a
* compile time generated Bose-Nelson sorting network.
* \tparam NumElements The number of elements in the array or container to sort.
* \tparam T The element type.
* \tparam Compare A comparator functor class that returns true if lhs < rhs.
*/
template <unsigned NumElements, class Compare = void> class StaticSort
{
template <class A, class C> struct Swap
{
template <class T> inline void s(T &v0, T &v1)
{
T t = Compare()(v0, v1) ? v0 : v1; // Min
v1 = Compare()(v0, v1) ? v1 : v0; // Max
v0 = t;
}
inline Swap(A &a, const int &i0, const int &i1) { s(a[i0], a[i1]); }
};
template <class A> struct Swap <A, void>
{
template <class T> inline void s(T &v0, T &v1)
{
// Explicitly code out the Min and Max to nudge the compiler
// to generate branchless code.
T t = v0 < v1 ? v0 : v1; // Min
v1 = v0 < v1 ? v1 : v0; // Max
v0 = t;
}
inline Swap(A &a, const int &i0, const int &i1) { s(a[i0], a[i1]); }
};
template <class A, class C, int I, int J, int X, int Y> struct PB
{
inline PB(A &a)
{
enum { L = X >> 1, M = (X & 1 ? Y : Y + 1) >> 1, IAddL = I + L, XSubL = X - L };
PB<A, C, I, J, L, M> p0(a);
PB<A, C, IAddL, J + M, XSubL, Y - M> p1(a);
PB<A, C, IAddL, J, XSubL, M> p2(a);
}
};
template <class A, class C, int I, int J> struct PB <A, C, I, J, 1, 1>
{
inline PB(A &a) { Swap<A, C> s(a, I - 1, J - 1); }
};
template <class A, class C, int I, int J> struct PB <A, C, I, J, 1, 2>
{
inline PB(A &a) { Swap<A, C> s0(a, I - 1, J); Swap<A, C> s1(a, I - 1, J - 1); }
};
template <class A, class C, int I, int J> struct PB <A, C, I, J, 2, 1>
{
inline PB(A &a) { Swap<A, C> s0(a, I - 1, J - 1); Swap<A, C> s1(a, I, J - 1); }
};
template <class A, class C, int I, int M, bool Stop = false> struct PS
{
inline PS(A &a)
{
enum { L = M >> 1, IAddL = I + L, MSubL = M - L};
PS<A, C, I, L, (L <= 1)> ps0(a);
PS<A, C, IAddL, MSubL, (MSubL <= 1)> ps1(a);
PB<A, C, I, IAddL, L, MSubL> pb(a);
}
};
template <class A, class C, int I, int M> struct PS <A, C, I, M, true>
{
inline PS(A &a) {}
};
public:
/**
* Sorts the array/container arr.
* \param arr The array/container to be sorted.
*/
template <class Container> inline void operator() (Container &arr) const
{
PS<Container, Compare, 1, NumElements, (NumElements <= 1)> ps(arr);
};
/**
* Sorts the array arr.
* \param arr The array to be sorted.
*/
template <class T> inline void operator() (T *arr) const
{
PS<T*, Compare, 1, NumElements, (NumElements <= 1)> ps(arr);
};
};
#include <iostream>
#include <vector>
int main(int argc, const char * argv[])
{
enum { NumValues = 10 };
// Arrays
{
int rands[NumValues];
for (int i = 0; i < NumValues; ++i) rands[i] = rand() % 100;
std::cout << "Before Sort: \t";
for (int i = 0; i < NumValues; ++i) std::cout << rands[i] << " ";
std::cout << "\n";
StaticSort<NumValues> staticSort;
staticSort(rands);
std::cout << "After Sort: \t";
for (int i = 0; i < NumValues; ++i) std::cout << rands[i] << " ";
std::cout << "\n";
}
std::cout << "\n";
// STL Vector
{
std::vector<int> rands(NumValues);
for (int i = 0; i < NumValues; ++i) rands[i] = rand() % 100;
std::cout << "Before Sort: \t";
for (int i = 0; i < NumValues; ++i) std::cout << rands[i] << " ";
std::cout << "\n";
StaticSort<NumValues> staticSort;
staticSort(rands);
std::cout << "After Sort: \t";
for (int i = 0; i < NumValues; ++i) std::cout << rands[i] << " ";
std::cout << "\n";
}
return 0;
}
请注意,我们没有使用 if (compare) swap
语句,而是显式地为 min 和 max 编写了三元运算符。这是为了帮助推动编译器使用无分支代码。
##基准
以下基准是使用 clang -O3
编译的,并在我 2012 年年中的 MacBook Air 上运行。
###对随机数据进行排序
将其与 DarioP 的代码进行比较,这里是对 100 万个大小为 10 的 32 位 int 数组进行排序所需的毫秒数:
硬编码排序网 10:88.774 毫秒模板化 Bose-Nelson 排序 10:27.815 毫秒
使用这种模板化方法,我们还可以在编译时为其他数量的元素生成排序网络。
对 100 万个不同大小的数组进行排序的时间(以毫秒为单位)。
大小为 2、4、8 的数组的毫秒数分别为 1.943、8.655、20.246。
https://i.stack.imgur.com/pR6Jo.png
归功于 Glenn Teitelbaum 的展开插入排序。
以下是 6 个元素的小数组的平均时钟。可以在这个问题上找到基准代码和示例:
Fastest sort of fixed length 6 int array
Direct call to qsort library function : 326.81
Naive implementation (insertion sort) : 132.98
Insertion Sort (Daniel Stutzbach) : 104.04
Insertion Sort Unrolled : 99.64
Insertion Sort Unrolled (Glenn Teitelbaum) : 81.55
Rank Order : 44.01
Rank Order with registers : 42.40
Sorting Networks (Daniel Stutzbach) : 88.06
Sorting Networks (Paul R) : 31.64
Sorting Networks 12 with Fast Swap : 29.68
Sorting Networks 12 reordered Swap : 28.61
Reordered Sorting Network w/ fast swap : 24.63
Templated Sorting Network (this class) : 25.37
对于 6 个元素,它的执行速度与问题中最快的示例一样快。
###排序数据的性能
通常,输入数组可能已经排序或大部分排序。在这种情况下,插入排序可能是更好的选择。
https://i.stack.imgur.com/yWouL.png
您可能需要根据数据选择适当的排序算法。
可在 here 中找到用于基准测试的代码。
v1 = v0 < v1 ? v1 : v0; // Max
仍然可能分支,在这种情况下它可以替换为 v1 += v0 - t
因为如果 t
是 v0
那么 v1 + v0 -t == v1 + v0 - v0 == v1
否则 t
是 v1
和 v1 + v0 -t == v1 + v0 - v1 == v0
maxss
或 minss
指令。但在它不起作用的情况下,可以使用其他交换方式。 :)
尽管网络排序在小型数组上速度很快,但如果经过适当优化,有时您无法击败插入排序。例如具有 2 个元素的批量插入:
{
final int a=in[0]<in[1]?in[0]:in[1];
final int b=in[0]<in[1]?in[1]:in[0];
in[0]=a;
in[1]=b;
}
for(int x=2;x<10;x+=2)
{
final int a=in[x]<in[x+1]?in[x]:in[x+1];
final int b=in[x]<in[x+1]?in[x+1]:in[x];
int y= x-1;
while(y>=0&&in[y]>b)
{
in[y+2]= in[y];
--y;
}
in[y+2]=b;
while(y>=0&&in[y]>a)
{
in[y+1]= in[y];
--y;
}
in[y+1]=a;
}
您可以完全展开 insertion sort。
为了使这更容易,可以使用递归模板而没有函数开销。由于它已经是一个模板,int
也可以是一个模板参数。这也使得创建 10 以外的编码数组大小变得微不足道。
请注意,要对 int x[10]
进行排序,调用是 insert_sort<int, 9>::sort(x);
,因为该类使用最后一项的索引。这可以被包装,但这将是更多需要阅读的代码。
template <class T, int NUM>
class insert_sort;
template <class T>
class insert_sort<T,0>
// Stop template recursion
// Sorting one item is a no operation
{
public:
static void place(T *x) {}
static void sort(T * x) {}
};
template <class T, int NUM>
class insert_sort
// Use template recursion to do insertion sort.
// NUM is the index of the last item, e.g. for x[10] call <9>
{
public:
static void place(T *x)
{
T t1=x[NUM-1];
T t2=x[NUM];
if (t1 > t2)
{
x[NUM-1]=t2;
x[NUM]=t1;
insert_sort<T,NUM-1>::place(x);
}
}
static void sort(T * x)
{
insert_sort<T,NUM-1>::sort(x); // Sort everything before
place(x); // Put this item in
}
};
在我的测试中,这比排序网络示例快。
由于与我描述的 here 类似的原因,以下排序函数 sort6_iterator()
和 sort10_iterator_local()
应该执行良好,其中排序网络取自 here:
template<class IterType>
inline void sort10_iterator(IterType it)
{
#define SORT2(x,y) {if(data##x>data##y)std::swap(data##x,data##y);}
#define DD1(a) auto data##a=*(data+a);
#define DD2(a,b) auto data##a=*(data+a), data##b=*(data+b);
#define CB1(a) *(data+a)=data##a;
#define CB2(a,b) *(data+a)=data##a;*(data+b)=data##b;
DD2(1,4) SORT2(1,4) DD2(7,8) SORT2(7,8) DD2(2,3) SORT2(2,3) DD2(5,6) SORT2(5,6) DD2(0,9) SORT2(0,9)
SORT2(2,5) SORT2(0,7) SORT2(8,9) SORT2(3,6)
SORT2(4,9) SORT2(0,1)
SORT2(0,2) CB1(0) SORT2(6,9) CB1(9) SORT2(3,5) SORT2(4,7) SORT2(1,8)
SORT2(3,4) SORT2(5,8) SORT2(6,7) SORT2(1,2)
SORT2(7,8) CB1(8) SORT2(1,3) CB1(1) SORT2(2,5) SORT2(4,6)
SORT2(2,3) CB1(2) SORT2(6,7) CB1(7) SORT2(4,5)
SORT2(3,4) CB2(3,4) SORT2(5,6) CB2(5,6)
#undef CB1
#undef CB2
#undef DD1
#undef DD2
#undef SORT2
}
为了调用这个函数,我向它传递了一个 std::vector
迭代器。
当你可以移动时为什么要交换?一个 x86 高速缓存行有足够的额外内存供您进行合并排序。
我可能会分别插入排序索引 0-1、2-4、5-6、7-9,或者甚至更好地保持这些小组在我插入时排序,以便每个插入最多需要一两个班次。
然后合并 5,6 和 7-9 -> 10-14,合并 0-1 和 2-4 -> 5-9,最后合并 5-9 和 10-14 -> 0-9
插入排序平均需要 29,6 次比较才能对 10 个输入进行排序,其中最好的情况是 9,最坏的情况是 45(给定的输入是倒序的)。
{9,6,1} shellsort 平均需要 25.5 次比较才能对 10 个输入进行排序。最好的情况是 14 次比较,最坏的情况是 34 次,对反向输入进行排序需要 22 次。
因此使用 shellsort 代替插入排序将平均情况减少了 14%。虽然最好的情况增加了 56%,但最坏的情况减少了 24%,这在控制最坏情况的性能很重要的应用中非常重要。反向情况减少了 51%。
由于您似乎熟悉插入排序,您可以将该算法实现为 {9,6} 的排序网络,然后在此之后添加插入排序 ({1}):
i[0] with i[9] // {9}
i[0] with i[6] // {6}
i[1] with i[7] // {6}
i[2] with i[8] // {6}
i[3] with i[9] // {6}
i[0 ... 9] // insertion sort
#define SORTPAIR(data, i1, i2) if (data[i1] > data[i2]) { int swap = data[i1]... }