我正在尝试创建一种非常节省空间的不寻常的关联数组实现,需要一个满足以下所有条件的排序算法:
- 稳定(不改变具有相同键的元素的相对顺序)。
- 原地或几乎原地(O(log n)堆栈大小是可以接受的,但不能使用O(n)空间或堆分配)。
- O(n log n)时间复杂度。
还要注意,要排序的数据结构是一个数组。
很容易看出,存在基本算法可以匹配其中任意两个条件(插入排序符合1和2,归并排序符合1和3,堆排序符合2和3),但我却找不到任何符合这三个条件的算法。
我正在尝试创建一种非常节省空间的不寻常的关联数组实现,需要一个满足以下所有条件的排序算法:
还要注意,要排序的数据结构是一个数组。
很容易看出,存在基本算法可以匹配其中任意两个条件(插入排序符合1和2,归并排序符合1和3,堆排序符合2和3),但我却找不到任何符合这三个条件的算法。
有一种方法可以解决这个问题,就是仔细选择一个被保证或至少非常可能接近中位数的中位数。令人惊讶的是,你实际上可以在线性时间内找到确切的中位数,但在你的情况下,我不建议这样做,因为你关心速度。
我认为最实用的方法是实现一个稳定的快速排序(很容易保持稳定),但在每个步骤中使用5个随机值的中位数作为枢轴。这使得你几乎不可能有慢速排序,并且是稳定的。
顺便说一下,归并排序可以原地进行,尽管同时进行原地和稳定排序有些棘手。
快速排序怎么样?
Exchange也可以做到这一点,按照您的术语可能更“稳定”,但快速排序速度更快。
有一类稳定的原地归并算法,虽然它们很复杂且线性时间复杂度下常数较高(O(n)),但是你可以阅读这篇文章以及它的参考文献来了解更多相关知识。
编辑:归并阶段是线性的,因此归并排序的时间复杂度是nlog_n。
通过为每个记录添加一个序列字段,将其初始化为排序之前的索引,并将其用作排序键的最低有效部分,可以相对容易地使快速排序保持稳定性。
这会略微影响所需的时间,但不会影响算法的时间复杂度。对于每个记录来说,它还需要一些额外的存储成本开销,但在记录数量很大时才会对性能产生实际影响(并且在更大的记录大小下可以最小化此成本)。
我使用了这种方法,通过为每个记录添加一个32位整数并在调用C
语言中的qsort()
函数之前用起始序列号填充它,避免编写自己的排序算法。
接着比较函数检查键值和序列号(这样可以确保没有重复的键值),将快速排序转变为稳定排序。我记得,对于我正在处理的数据集,它仍然优于内置的稳定归并排序算法。
由于数据情况各异,因此请永远记住:量入为出,不要臆测!
由于您的元素是在一个数组中(而不是链表),因此您可以利用数组索引本身来获得有关它们原始顺序的一些信息。您可以通过编写排序和比较函数使它们意识到这一点来利用它:
function cmp( ar, idx1, idx2 )
{
// first compare elements as usual
rc = (ar[idx1]<ar[idx2]) ? -1 : ( (ar[idx1]>ar[idx2]) ? 1 : 0 );
// if the elements are identical, then compare their positions
if( rc != 0 )
rc = (idx1<idx2) ? -1 : ((idx1>idx2) ? 1 : 0);
return rc;
}
只要排序算法仅涉及元素交换,此技术即可用于使任何类型的排序稳定。元素的索引将会改变,但相同元素的相对顺序将保持不变,因此排序算法仍然是鲁棒的。这种技术无法直接用于像堆排序这样的排序算法,因为原始堆化操作“抛弃”了元素之间的相对顺序,尽管您可能能够将此想法应用于其他排序算法。
在你能够证明它很重要之前,不要过于担心O(n log n)。如果你能找到一个具有极低常数的O(n^2)算法,那就采用它!
通常最坏情况并不相关,如果数据高度受限。
简而言之:进行一些测试。