排序10个数字的最快方法?(数字为32位)


211

我正在解决一个问题,它涉及非常快速地排序10个数字(int32)。我的应用程序需要尽可能快地对10个数字进行数百万次排序。我正在采样数十亿个元素的数据集,每次需要从中选择10个数字(简化)并对它们进行排序(并从排序的10个元素列表中得出结论)。

目前,我正在使用插入排序,但我想我可以针对10个数字的特定问题实现一个非常快速的自定义排序算法,该算法将胜过插入排序。

有谁知道如何解决这个问题?


14
听起来很粗糙,一系列嵌套if语句应该是最好的。避免循环。
2015年

8
您是否希望这些数字会在排列组合中带有任何偏差,或者它们会均匀分布?一个列表的排序与下一个列表的排序之间会有任何关系吗?
道格拉斯·扎里

4
整个数据集(具有数十亿个数字)是根据Benford定律分布的,但是当我从该集合中随机选择元素时,它们不再是(我认为)。
bodacydo

13
您可能需要阅读一下stackoverflow.com/q/2786899/995714
phuclv

11
如果您从数十亿个元素中随机选择,那么即使将整个数据集放在RAM中,拉入数据的延迟也可能比对选定元素进行排序所需的时间影响更大。您可以通过基准测试性能来测试影响,依次选择数据还是随机选择数据。
Steve S.

Answers:


213

(按照HelloWorld的建议调查排序网络。)

似乎29比较/交换网络是进行10输入排序的最快方法。我使用了Waksman在1969年发现的网络作为Javascript的示例,该网络应直接转换为C,因为它只是if语句,比较和交换的列表。

function sortNet10(data) {	// ten-input sorting network by Waksman, 1969
    var 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; }
    return(data);
}

alert(sortNet10([5,7,1,8,4,3,6,9,2,0]));

这是网络的图形表示,分为独立的阶段。 要利用并行处理,可以将5-4-3-4-4-4-3-2分组更改为4-4-4-4-4-4-3-3-2分组。
10输入分拣网络(Waksman,1969)

重新组合了10输入分类网(Waksman,1969)


69
建议; 使用交换宏。像#define SORTPAIR(data, i1, i2) if (data[i1] > data[i2]) { int swap = data[i1]... }
彼得·科德斯

9
可以从逻辑上证明这是最小值吗?
corsiKa

8
@corsiKa是的,自计算机科学成立以来,排序网络一直是研究领域。在许多情况下,最佳解决方案已有数十年的历史了。参见en.wikipedia.org/wiki/Sorting_network
m69“生气和不受欢迎”

8
我做了一个Jsperf进行测试,可以确认Network Sort的速度是浏览器本机排序的20倍以上。 jsperf.com/fastest-10-number-sort
丹尼尔(Daniel)

9
@Katai这将破坏编译器可能产生的任何优化。馊主意。阅读更多信息en.wikipedia.org/wiki/...
Antzi

88

当您处理此固定大小时,请查看“ 排序网络”。这些算法具有固定的运行时间,并且独立于其输入。对于您的用例,您没有某些排序算法所需的开销。

双音排序是这种网络的一种实现。这在len(n)<= 32的CPU上效果最好。在较大的输入上,您可能会考虑迁移到GPU。 https://zh.wikipedia.org/wiki/排序网络

顺便说一句,这里是比较排序算法的一个不错的页面(尽管它缺少bitonic sort

http://www.sorting-algorithms.com


3
@ ErickG.Hagstrom有很多解决方案。只要使用29个比较,它们的效率就相等。我从1969年开始使用Waksman的解决方案。他显然是第一个发现29个对比版本的人。
m69“不客气”

1
是的,@ m69。有超过一百万。Waksman解决方案的长度为29,深度为9。我链接的解决方案是对深度尺寸的改进:长度= 29,深度=8。当然,在C语言中实现时,深度无关紧要。
Erick G. Hagstrom

4
@ ErickG.Hagstrom显然,深度为7的解决方案有87个,其中第一个由Knuth于1973年发现,但我无法通过快速的Google找到其中的一个。larc.unt.edu/ian/pubs/9-input.pdf(请参见结论,第14页)
m69“陈旧而不受欢迎”,2015年

4
@ ErickG.Hagstrom:深度在“ C级别”可能没有什么区别,但是大概一旦编译器和CPU完成后,它就有可能在CPU中部分并行化,因此较小的深度可能会有所帮助。当然,取决于CPU:某些CPU相对简单并且可以一举一动地进行,而某些CPU可以在运行中进行多种操作,尤其是对于在Windows中所需的任何装载和存储,您可能会获得截然不同的性能。为了操作10个变量,具体取决于它们的完成方式。
史蒂夫·杰索普

1
@ ErickG.Hagstrom伊恩·帕伯里(Ian Parberry)的论文还没有立即弄清楚,但是深度7网络的长度大于29。请参见Knuth,“计算机编程的艺术,第三卷”,第5.3.4节,图。49和51
M69“”尖刻和不欢迎“”

33

使用排序比较为4组的排序网络,因此可以在SIMD寄存器中进行。一对压缩的最小/最大指令实现了压缩的比较器功能。抱歉,我现在没有时间寻找我记得看到的页面,但是希望在SIMD或SSE排序网络上进行搜索会有所帮助。

x86 SSE确实具有用于四个32位整数的向量的packet-32bit-integer min和max指令。AVX2(Haswell和更高版本)具有相同的功能,但适用于8个整数的256b向量。也有有效的随机播放指令。

如果您有很多独立的小排序,则可以使用向量并行进行4或8个排序。Esp。如果您随机选择元素(因此要排序的数据无论如何都不会在内存中连续),则可以避免随机排序,而只需按所需顺序进行比较即可。10个寄存器来保存来自10个整数的4个(AVX2:8)列表中的所有数据,仍然留下6个暂存空间。

如果您还需要对关联数据进行排序,则矢量排序网络的效率会降低。在那种情况下,最有效的方法似乎是使用压缩比较来获取更改了哪些元素的掩码,并使用该掩码来混合(引用)关联数据的向量。


26

展开的无分支选择排序怎么样?

#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

4
结果不是很令人印象深刻,但实际上是我所期望的。分类网络可最大程度地减少比较,而不是交换。当所有值都已在缓存中时,比较要比交换便宜得多,因此选择排序(将交换次数最小化)占了上风。(并且没有更多的比较:具有29个同伴,最多29个掉期的网络?;与具有45个比较和最多9个掉期的选择排序)
例如

7
哦,它确实有分支-除非对行for ( ; i<10; i++) (m > a[i]) && (m = a[i], indx = i ); 进行了特别好的优化。(短路通常是分支的一种形式)
例如

1
@EugeneRyabtsev也是,但是它总是被喂以完全相同的随机序列,因此应该取消。我试图改变std::shufflefor (int n = 0; n<10; n++) a[n]=g();。现在执行时间减半,网络速度更快。
DarioP

与libc ++的相比如何std::sort
gnzlbg 2015年

1
@gnzlbg我也尝试过std::sort,但是它的表现太差了,以至于我什至没有将其包括在基准测试中。我猜只有很小的数据集会产生很大的开销。
DarioP's

20

问题并不是说这是某种基于Web的应用程序。引起我注意的一件事是:

我采样了数十亿个元素的数据集,每次需要从中选择10个数字(简化)并对它们排序(并从排序的10个元素列表中得出结论)。

作为软件和硬件工程师,这绝对使我大喊“ FPGA”。我不知道您需要从排序后的数字集合中得出什么样的结论,或者数据来自何处,但是我知道处理这些亿万到十亿之间的“排序和-”操作几乎是微不足道的。分析”操作每秒。过去,我已经完成了FPGA辅助的DNA测序工作。当问题非常适合那种解决方案时,几乎不可能击败FPGA的强大处理能力。

在某种程度上,唯一的限制因素是将数据输入到FPGA的速度以及输出数据的速度。

作为参考,我设计了一种高性能实时图像处理器,该处理器以每秒约3亿像素的速度接收32位RGB图像数据。数据通过FIR滤波器,矩阵乘法器,查找表,空间边缘检测块和许多其他操作流式传输后,再传到另一端。所有这些都在一个相对较小的Xilinx Virtex2 FPGA上进行,内部时钟范围从大约33MHz到400MHz(如果我没有记错的话)。哦,是的,它也有一个DDR2控制器实现并运行了两排DDR2内存。

FPGA在数百MHz的频率下运行时,每个时钟转换都可以输出十个32位数字。由于数据填充处理流水线,因此在操作开始时会有短暂的延迟。之后,您应该能够每个时钟获得一个结果。如果可以通过复制排序和分析管道来并行化处理,则更多。原则上,该解决方案几乎是微不足道的。

关键是:如果应用程序不是PC绑定的,并且数据流和处理与FPGA解决方案“兼容”(独立或作为机器中的协处理器卡),那么您将无路可走无论使用哪种算法,都可以用任何语言编写的软件都可以达到可达到的性能水平。

编辑:

只需进行快速搜索,即可找到可能对您有用的论文。看起来它可以追溯到2012年。您可以在今天(甚至在那时)获得更好的性能。这里是:

FPGA上的排序网络


10

我最近写了一个小类,它使用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毫秒

使用这种模板化方法,我们还可以在编译时为其他数量的元素生成排序网络。

对一百万个各种大小的数组进行排序的时间(以毫秒为单位)。
大小为2、4、8的数组的毫秒数分别为1.943、8.655和20.246。
C ++模板化Bose-Nelson静态排序时间

归功于Glenn Teitelbaum展开的插入排序。

这是6个元素的小型阵列的每种时钟的平均时钟。可以在以下问题找到基准代码和示例:
最快的固定长度6 int数组类型

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个元素,它的执行速度与问题中最快的示例一样快。

排序数据的性能

通常,输入数组可能已经排序或大部分排序。
在这种情况下,插入排序可能是更好的选择。

在此处输入图片说明

您可能要根据数据选择适当的排序算法。

可在此处找到用于基准测试的代码。


您是否有机会在下面为我的算法添加比较?
Glenn Teitelbaum

@GlennTeitelbaum你添加到了任何机会,您的标准和披露方式和结果?
灰胡子

在对已排序输入进行排序时添加数据的荣誉。
灰胡子

在某些系统v1 = v0 < v1 ? v1 : v0; // Max仍然可以分支,在这种情况下,它可以被替换v1 += v0 - t,因为如果tv0那么v1 + v0 -t == v1 + v0 - v0 == v1别人tv1v1 + v0 -t == v1 + v0 - v1 == v0
格伦泰特鲍姆

三元通常在现代编译器上编译为maxssminss指令。但是在无法正常工作的情况下,可以使用其他交换方式。:)
矢量化

5

尽管网络排序在小型阵列上具有很高的几率,但如果进行适当的优化,有时您也无法击败插入排序。例如,具有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;
}

不知道为什么重复in[y+2]= in[y];,错别字?
Glenn Teitelbaum

哇,我是怎么做到的?有人花了这么长时间注意到了什么?答案:这不是打字错误:我正在改编同时具有键和值数组的另一种算法。
沃伦

3

您可以完全展开 insertion sort

为了简化操作,template可以使用递归s,而不会增加函数开销。由于它已经是一个templateint也可以是一个template参数。这也使得创建除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 1 item is a no-op
{
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, eg. 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
    }
};

在我的测试中,这比排序网络示例要快。


0

出于与我在此处所述类似的原因,以下排序功能sort6_iterator()sort10_iterator_local()应该在从此处获取排序网络的地方应能很好地执行:

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迭代器。


0

插入排序平均需要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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.