C Array sorting tips
这是对以下数组进行排序的最佳排序技术,如果存在重复项,如何处理它们。这也是最好的分类技术…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| void BubbleSort(int a[], int array_size)
{
int i, j, temp;
for (i = 0; i < (array_size - 1); ++i)
{
for (j = 0; j < array_size - 1 - i; ++j )
{
if (a[j] > a[j+1])
{
temp = a[j+1];
a[j+1] = a[j];
a[j] = temp;
}
}
}
} |
号
- 参见:en.wikipedia.org/wiki/sorting_algorithm
- 没有"最好的排序技术",这取决于数据的大小以及是否在开始时进行了排序。我建议你也阅读en.wikipedia.org/wiki/&hellip;和整个wikipedia文章。
- "最佳"取决于数据和其他限制:内存、速度、如何开始排序错误。流沙是其中一个巨大的妥协。气泡排序最适合小内存。你想完成什么?
- 最好的(如果best==fastest)排序技术是获取已经排序的数据。
- "Following Array"="前面的阵列"?如果是的话,最快的方法就是把它按顺序写下来。说真的,我在生成的代码中这样做。
- @Drewk:气泡排序对小内存来说不是最好的选择。它唯一的优点是它是一种就地排序,但对于快速排序、堆排序和其他排序也是如此,所有这些都是O(n.log(n)),而不是O(n^2)类型的冒泡排序。
- @克里斯:同意。我说的是最小内存,我的意思是"最简单"。气泡可能是最简单的代码或概念。一些排序算法可以获得博士级的深奥和难以理解。有些可能在边缘情况下变得不稳定或崩溃。泡沫分类可能不是最小的,当然也不是最快的,但我敢打赌我8岁的孩子能理解!
- @德雷克:我不太确定,你应该试试看。有些算法确实很深奥,但另一些则非常直观。我恰巧向我6岁的女儿解释了"快速排序"(按发行号对一堆杂志进行排序),她完全理解。用气泡分类法很难避免原位的东西,我首先要解释她的计算机阵列…顺便说一下,基数排序对非计算机用户来说更简单。只需将其称为"邮差分类",然后说"为每年制作一个堆栈"和"以后在每年的堆栈中为每月制作一个堆栈"之类的话。
- @Drewk:Bubble排序不是"适合小内存"。它的速度很慢,因为hell和heap sort在运行非常快(即O(n log n))的情况下使用相同数量的内存(即,它已就位)。
在C语言中,您可以使用内置的qsort命令:
1 2 3 4 5 6 7 8 9 10 11
| int compare ( const void* a , const void* b )
{
int int_a = * ( (int*) a );
int int_b = * ( (int*) b );
if ( int_a == int_b ) return 0;
else if ( int_a < int_b ) return -1;
else return 1;
}
qsort( a , 6, sizeof(int), compare ) |
参见:http://www.cplusplus.com/reference/clibrary/cstdlib/qsort/
要回答问题的第二部分:最佳(基于比较)排序算法是一种运行O(n log(n))比较的排序算法。有几个具有此属性(包括快速排序、合并排序、堆排序等),但要使用哪个属性取决于您的用例。
作为旁注,如果你对你的数据有所了解,你有时可以做得比O(n log(n))更好——参见维基百科关于基数排序的文章。
- @亚历克斯:如果你想快点,至少要提供一个像样的比较功能!qsort不需要返回值为-1,0,1,而是"任何负数",0,"任何正数",因此您只需要执行return *((int*)a)-*((int*)b);,这比您的建议快得多。
- @克里斯:在整数溢出的情况下,您的比较没有很好的定义;因此,人们经常看到像return (a > b) - (a < b)这样的东西。
- @克丽丝:只是比较函数不起作用。例如,如果a是INT_MAX,而b是-1,会发生什么?
- @斯蒂芬·卡农:同意,当你不知道你的数据范围和溢出可能发生时,你应该使用像克里斯托夫的公式。在实际情况下,我在处理有符号数字时从未见过一次,但我对数据范围没有大致的概念(我的公式也适用于无符号)。我的观点主要是CompareAPI结果类型不是-1,0,1(或者我们甚至不能使用strcmp来比较char*)。
- 快速分拣不在O(n log n)时间内运行。它在O(n^2)时间运行。那些声称不这样做的人需要看看"大O"是什么意思。
- @克里斯:由于整数溢出问题,返回差异不起作用!除非您擅长避免溢出,否则最好只使用条件和返回-1/0/1,这样更不容易出错。
- 需要注意的一点是:只要数据类型的范围严格小于int,take-the-difference方法就可以工作。
- @R.:使用Quicksort的幼稚实现,当您总是以say the first item为轴心,并且在寻找最坏情况的复杂性时,它确实是O(n^2)。但随机数据证明,Quicksort的复杂性平均为O(n.log(n))(这可能是人们声称Quicksort是O(n.log(n))所声称的)。大O符号并不意味着你在说最坏的情况。第二,对quicksort的微小更改(如此微小的更改仍然称为quicksort)可以使它在最坏的情况下成为o(n.log(n))。
- @R…:你可能还没看过我上面的评论。对于真实数据,您通常在比int域小得多的域中工作。但是,当用C编程时,人们只需将其类型称为int,即使它们不是真正的int,并且数据永远不会超出域。在许多情况下,域限制保证永远不会溢出。在这种情况下(确实有限制,但有可接受的限制),该方法工作正常。另一个类似的典型情况是,如果知道输入值在0..int_max(而不是uint_max)范围内,则使用unsigneds。
- @克里斯:这种符号的使用是完全错误的。即使是随机化的,它也会碰到需要二次时间的情况。因此,大O是二次的。大O总是意味着最坏的情况。对荒谬的"平均情况"复杂度估计使用不同的符号。
- @R.:是的,它的作用是价值域足够小,通常情况下是这样的。对于无符号的,如果两个无符号都小于int_max(而不是uint_max),它就会工作。通常我在需要(实际)速度或位级别控制时使用C编程语言。如果问题存在于其他地方(算法的复杂性或类似的问题),我通常会选择Python。但好吧,当你做这些事情的时候,你最好确切地知道你在做什么。
- @r.:big o并不意味着最坏的情况,quicksort平均为o(n logn),而幼稚的实现可以通过在最坏的情况下相同的方式进行更改(只需要从更多的值中选择一个透视)。更改足够小,修改后的版本通常仍称为快速排序。
- @克丽丝:如果我说一个算法是时间上的O(f(n)),那意味着它运行的时间被f(n)的一个常数倍数所限制,在这个常数中,对于所有可能的输入,特定的常数依赖于实现,但在实现中是不变的。声称"快速排序"是O(n log n),与声称"if (rand()==42) return find_prime_factors(n); else return NULL;"是"O(1)"是"n"一样荒谬。
- @R.实际上,有一个Quicksort版本保证在O(n log n)中运行-使用QuickSelect和中间值5来找到O(n)中的真正中间值,然后在适当的一半上重复。T(n) = n + 2T(n/2) = O( n log n )。
- @R.:您是否混淆了数据和数据大小?big-o符号参数是数据的大小,但平均值是数据。看看en.wikipedia.org/wiki/&hellip;。我在大学时记得的是:考虑函数f()的所有可能输入。有些(T1)有一个k1*n*log(n)运行时,有些(T2)退化,是k2*n^2运行时。设r(n)为t2例数。average O of f(n)为O(((n-r(n))*k2*n^2 + r(n)*k1*n*log(n))/n)表示退化病例数为对数(n)或小于平均数O(f(n)) = O(log(n)*n)。
- @亚历克斯:好吧,我在维基百科上找到了参考资料,很明显,Quicksort的变体可以在O(n log n)时间运行。我认为这是一个足够先进的算法,它不等于它与快速排序,但枢纽原则是相同的。
- @克丽丝:平均值是完全不相关的。大O是一个边界问题,与平均性能无关。我在rand()中的例子是,编写一个函数很容易,其中平均性能很快,但最坏的情况是任意缓慢。正如亚历克斯所指出的,很明显有可能制造一种在O(n log n)时间内运行的Quicksort变体,但是您对big o术语的使用仍然是不正确的。
- @R.:嗯,我觉得我上面的例子已经足够了,但似乎不够(你读过吗?)我想我得先回答一个问题。我很好奇你能在很多网站上找到完整的数学和细节。大O通常被用作"最坏的情况",因为如果最坏的情况具有很好的复杂性,那么运行时间就很低。但是,如果您致力于脚本分析,您将要寻找的是"最佳情况",因为您希望您的代码对于任何输入都很难被破坏。平均情况介于之间,平均后应用大O。
在您的特定情况下,最快的排序可能是这个答案中描述的排序。它针对6个整数的数组进行了精确优化,并使用了排序网络。它比库qsort快20倍(在x86上测量)。对于固定长度数组的排序,排序网络是最佳的。因为它们是一个固定的指令序列,所以它们甚至可以很容易地由硬件实现。
一般来说,有许多针对特定情况优化的排序算法。诸如堆排序或快速排序之类的通用算法针对项目数组的就地排序进行了优化。它们产生O(n.log(n))的复杂性,n是要排序的项目数。
库函数qsort()的编码非常好,并且在复杂性方面效率很高,但是使用了对用户提供的某些comparizon函数的调用,并且此调用的成本相当高。
为了对大量数据进行排序,算法还必须处理与磁盘之间的数据交换,这是在数据库中实现的一种排序,如果您有这样的需要,最好是将数据放入某些数据库并使用内置排序。
视情况而定
这取决于各种因素。但一般来说,使用分治/二分法的算法在排序问题时会表现良好,因为它们呈现出有趣的平均情况复杂性。
基础
要了解哪种算法最有效,您需要了解算法复杂性和big-o符号的基本知识,以便了解它们在平均情况、最佳情况和最坏情况下的比率。如果需要,还必须注意排序算法的稳定性。
例如,通常一个有效的算法是快速排序。但是,如果您给QuickSort一个完全颠倒的列表,那么它将执行得很糟糕(在这种情况下,简单的选择排序将执行得更好!).shell排序通常也是Quicksort的一个很好的补充,如果您对列表进行预分析。
使用分而治之的方法进行"高级搜索",请查看以下内容:
对于不太复杂的算法,这些更为严格的算法:
进一步
以上是开始时的常见疑点,但还有无数其他疑点。
正如R.在评论中和Kris在回答中指出的那样,您可能想看看Heapsort,它提供了理论上比快速排序更好的排序复杂性(但在实际情况下通常不会更好)。还有变体和混合算法(如timsort)。
- 如果您向QuickSort提供一个完全颠倒的列表,那么它只会在最简单的实现中退化(所有方法都以列表的头部为轴心),即使这样,冒泡排序也不会更糟。对于已经排序的列表,简单的快速排序也会表现不佳。但是,对算法进行非常简单的更改就足以避免这个问题(从列表中提取几个数字作为潜在的轴,并选择中间值作为轴)。
- @克里斯:没错。但这是一个CS学习问题,所以我只讨论这些方法的理论和基本实现。显然,您可以调整算法并将这些副作用最小化,但是当操作人员询问一般的排序问题时,我认为确定这些问题更符合实际。
- @Haylem:这确实是一个学习问题,但是对于读者来说,谈论幼稚实现的风险在于相信称为qsort的库是Quicksort的幼稚实现,而它不是,并且会在排序的数据集上退化。如果我记得正确的话,在大多数实现中它甚至不是一个快速排序。
- 您省略了堆排序,这可以说是理想的排序(O(1)空间和O(n log n)时间)。
- @克丽丝:谢谢你的纠正。
- @R:我想我漏掉了很多人:)但你是对的,我应该提到堆排序。
最好的排序技术通常取决于数组的大小。合并排序可以是最好的,因为它根据big-o算法管理更好的空间和时间复杂性(这更适合于大型数组)。
我想做些改变:在C中,可以使用内置的qsort命令:
1 2 3 4 5 6 7 8 9 10
| int compare ( const void* a , const void* b )
{
int int_a = * ( (int*) a );
int int_b = * ( (int*) b );
// an easy expression for comparing
return (int_a > int_b ) - (int_a < int_b );
}
qsort( a , 6, sizeof(int), compare ) |
号