第一个和最后一个枢轴元素与具有非常大 N 的通用放置
Posted
技术标签:
【中文标题】第一个和最后一个枢轴元素与具有非常大 N 的通用放置【英文标题】:First and last pivot element vs generic placement with very large N 【发布时间】:2022-01-14 04:40:07 【问题描述】:我已经实现了快速排序算法和时间复杂度控件。它适用于较小的 N,但一旦我接近较大的 N,*** 是不可避免的。我认为将枢轴元素作为最后一个元素可能是造成这种情况的原因。
我的第一个想法是简单地始终使用中间元素作为枢轴元素来避免这种情况,但由于测试程序抛出“未排序的异常”,这不是一个有效的解决方案。
有什么想法可以解决这个问题吗?
public class QuickSorter implements IntSorter
int partition (int a[], int lo, int hi)
int pivot = a[hi]; // pivot element
int i = (lo - 1);
for (int j = lo; j <= hi - 1; j++)
if (a[j] < pivot)
i++;
int temp = a[i];
a[i] = a[j];
a[j] = temp;
int temp = a[i+1];
a[i+1] = a[hi];
a[hi] = temp;
return (i + 1);
@Override
public void sort(int[] a)
int lo = 0;
int hi = a.length-1;
if (lo < hi)
int p = partition(a, lo, hi);
sort(a, lo, p - 1);
sort(a, p + 1, hi);
private void sort(int[] a, int lo, int hi)
if (lo < hi)
int p = partition(a, lo, hi);
sort(a, lo, p - 1);
sort(a, p + 1, hi);
测试代码:
private static void testSort(IntSorter sorter, int firstN, boolean ordered)
double t1 = 0;
int N = firstN/2;
while (t1 < 0.7 && N < 10000000)
N *= 2;
int[] a = create(N, ordered);
t1 = timeit(sorter, a);
System.out.println("T("+N+")="+t1);
ArrayUtil.testOrdered(a);
int[] a = create(4*N, ordered);
double t4 = timeit(sorter, a);
ArrayUtil.testOrdered(a);
double t01 = t1 / (N * Math.log(N ));
double t04 = t4 / (4*N * Math.log(4*N));
System.out.println("T("+4*N+")="+t4+" growth per N log N: "+t04/t01);
if (t04/t01 > 1.25)
System.out.println(sorter.getClass().getName()+".sort appears not to run in O(N log N) time");
System.exit(1);
public static void testOrdered(int[] a)
int N = a.length;
for (int i = 1; i < N; i++)
if (a[i] < a[i-1])
throw new SortingException("Not sorted, a["+(i-1)+"] > a["+i+"]");
【问题讨论】:
Quicksort 应该适用于任何枢轴,尽管运行时特性可能会发生变化(例如,如果数组已经非常有序,则选择最后一个元素可能会导致大量递归)。如果您选择中间元素时您的算法没有正确排序,那么它是错误的,您需要检查(使用调试器逐步检查代码)。 顺便说一句,你可以使用j <= hi - 1
而不是 j < hi
。
@Thomas 我明白了!但是从某种意义上说,我是否正确,导致***错误的事情实际上是使用最后一个元素时发生的递归量?
这必须分析,但很有可能。想想如果您尝试对像 [1,2,3,4,5] 这样的数组进行排序并将最后一个元素作为枢轴会发生什么。你最终会得到空的“更大”数组和“更小”数组,如 [1,2,3,4]、[1,2,3]、[1,2]、[1],即你会这样做在最坏的情况下对每个元素进行一次递归调用。当然,如果中间元素恰好是最大的(例如,如果数组是 [2,4,5,3,1](这将导致“较低”数组 [2,4, 3,1], [2,3,1], [2,1], [1]) 但不太可能以这种方式“排序”。
在任何情况下,无论您选择哪个元素作为枢轴,递归都可能受到堆栈溢出的影响,您可以减少这种情况发生的可能性(已经排序或几乎排序的数组比“怪胎”命令)。如果您想完全消除这种风险,您应该考虑用迭代替换递归(这可能会帮助您了解这个想法:techiedelight.com/iterative-implementation-of-quicksort)。
【参考方案1】:
作为 Thomas cmets,使用中间元素作为枢轴应该可以正常工作。实际上,这是一个常见的选择,因为它适用于恰好已经完全或部分排序的输入数组。
至于避免堆栈溢出,一种常见的方法是在分区步骤之后仅在较短的部分上递归 - 这可以确保在每个级别处理的数组至少减半,例如1,000,000 元素数组的最大调用深度约为 20 (log2(1,000,000))。
所以,而不是
private void sort(int[] a, int lo, int hi)
if (lo < hi)
int p = partition(a, lo, hi);
sort(a, lo, p - 1);
sort(a, p + 1, hi);
你会的
private void sort(int[] a, int lo, int hi)
while (lo < hi)
int p = partition(a, lo, hi);
// recurse on smaller part, loop on larger part
if (((p - 1) - lo) > (hi - (p + 1)))
sort(a, p + 1, hi);
hi = p - 1;
else
sort(a, lo, p - 1);
lo = p + 1;
【讨论】:
是的,效果很好,谢谢!我还尝试在排序之前对数组进行洗牌,这似乎也有效。 :)【参考方案2】:在排序之前用下面的方法对数组进行洗牌似乎也解决了我遇到的问题
public void shuffle(int[] a)
int N = a.length;
Random randomGenerator = new Random();
for (int i = 0; i < N; i++)
int r = i + randomGenerator.nextInt(N-i); // between i and N-1
int t = a[i]; a[i] = a[r]; a[r] = t;
【讨论】:
以上是关于第一个和最后一个枢轴元素与具有非常大 N 的通用放置的主要内容,如果未能解决你的问题,请参考以下文章