我现在正在学习不同类型的排序,我发现,从某一点开始,我的QuickSort算法根本不能快速运行。
这是我的代码:
class QuickSort
{
// partitioning array on the key so that the left part is <=key, right part > key
private int Partition(int[] arr, int start, int end)
{
int key = arr[end];
int i = start - 1;
for (int j = start; j < end; j++)
{
if (arr[j] <= key) Swap(ref arr[++i], ref arr[j]);
}
Swap(ref arr[++i], ref arr[end]);
return i;
}
// sorting
public void QuickSorting(int[] arr, int start, int end)
{
if (start < end)
{
int key = Partition(arr, start, end);
QuickSorting(arr, start, key - 1);
QuickSorting(arr, key + 1, end);
}
}
}
class Test
{
static void Main(string[] args)
{
QuickSort quick = new QuickSort();
Random rnd = new Random(DateTime.Now.Millisecond);
int[] array = new int[1000000];
for (int i = 0; i < 1000000; i++)
{
int i_rnd = rnd.Next(1, 1000);
array[i] = i_rnd;
}
quick.QuickSorting(array, 0, array.Length - 1);
}
}
在一百万个元素的数组上运行此代码大约需要15秒。例如,MergeSort或HeapSort在不到一秒的时间内完成相同的操作。
请您解释一下为什么会发生这种情况?
答案 0 :(得分:4)
您的排序速度有多快以及您应该使用哪种算法取决于您输入的大量数据。它是随机的,几乎排序的,反转的等等。
有一个非常好的页面,说明了不同的排序算法如何工作:
答案 1 :(得分:2)
您是否考虑过内联Swap
方法?这样做应该不难,但可能是JIT发现难以内联。
当我implemented quicksort for Edulinq我根本没有看到这个问题时 - 您可能想尝试我的代码(可能是最简单的递归形式),看看它对您的表现如何。如果做得好,请尝试找出差异所在。
虽然不同的算法对同一数据的行为会有所不同,但我不希望看到这个在随机生成的数据上存在很大差异。
答案 2 :(得分:1)
您有1,000,000个随机元素,其中包含1,000个不同的值。因此,我们可以预期大多数值在您的数组中出现约1,000次。这为您提供了一些二次O(n ^ 2)运行时间。
要将数组分区为1,000个,其中每个分区包含相同的数字,发生在大约log2(1000)的堆栈深度,大约为10.(假设对分区的调用整齐地将其分成两部分。 )那是大约10,000,000次操作。
快速排序最后1,000个分区,所有分区都包含1,000个相同的值。我们需要1000次1,000 + 999 + 998 + ... + 1次比较。 (在每一轮快速排序中将问题减少一个,只删除键/枢轴。)这样可以提供500,000,000个操作。快速排序1,000分区的理想方式将是1,000次1,000 * 10次操作= 10,000,000次。由于相同的值,你在这里遇到了二次方案,即quicksort的最差情况。因此,在快速排序的一半左右,它会出现最糟糕的情况。
如果每个值只出现几次,那么在O(N^2)
或O(N logN)
中对这些微小分区进行排序并不重要。但是在这里我们有很多很大的分区需要在O(N^2)
中进行排序。
改进您的代码:3件分区。比枢轴小,等于枢轴并且比枢轴大。然后,只快速排序第一个和最后一个分区。你需要做一个额外的比较;先测试平等。但我认为,对于这种输入,它会快得多。