Java 快速排序二次运行时行为

Java Quicksort quadratic runtime behaviour

我试图在 Java 中实现一个高效的排序算法。为此,我还实现了快速排序,使用了如下代码:

public class Sorting {
    private static Random prng;

    private static Random getPrng() {
        if (prng == null) {
            prng = new Random();
        }
        return prng;
    }

    public static void sort(int[] array) {
        sortInternal(array, 0, array.length - 1);
    }

    public static void sortInternal(int[] array, int start, int end) {
        if (end - start < 50) {
            insertionSortInternal(array, start, end);
        } else {
            quickSortInternal(array, start, end);
        }
    }

    private static void insertionSortInternal(int[] array, int start, int end) {
        for (int i=start; i<end - 1; ++i) {
            for (int ptr=i; ptr>0 && array[ptr - 1] < array[ptr]; ptr--) {
                ArrayUtilities.swap(array, ptr, ptr - 1);
            }
        }
    }

    private static void quickSortInternal(int[] array, int start, int end) {
        int pivotPos = getPrng().nextInt(end - start);
        int pivot = array[start + pivotPos];
        ArrayUtilities.swap(array, start + pivotPos, end - 1);
        int left = start;
        int right = end - 2;
        while (left < right) {
            while (array[left] <= pivot && left < right) {
                ++left;
            }
            if (left == right) break;
            while (array[right] >= pivot && left < right) {
                right--;
            }
            if (left == right) break;
            ArrayUtilities.swap(array, left, right);
        }
        ArrayUtilities.swap(array, left, end - 1);
        sortInternal(array, start, left);
        sortInternal(array, left + 1, end);
    }
}

ArrayUtilities.swap 只是交换数组中的两个给定元素。从这段代码中,我预计 O(n log(n)) 运行时行为。但是,一些不同长度的数组排序给出了以下结果:

10000 个元素:32 毫秒

20000 个元素:128 毫秒

30000 个元素:296 毫秒

每种情况测试运行次100次,然后计算运行次的算术平均值。但很明显,与预期的行为相反,运行时间是 O(n^2)。我的算法有什么问题?

在您的插入排序实现中,您的数组将按降序排序,而在您的快速排序中,数组将按升序排序。所以替换(降序):

for (int ptr=i; ptr>0 && array[ptr - 1] < array[ptr]; ptr--)

for (int ptr=i; ptr>0 && array[ptr - 1] > array[ptr]; ptr--)

您的索引似乎也不正确。 尝试替换:

sortInternal(array, 0, array.length - 1);

与:

sortInternal(array, 0, array.length);

并且在插入排序中首先 for 循环你不需要做 end - 1,即使用:

for (int i=start; i<end; ++i)

最后,在快速排序方法的开头添加if (start >= end) return;

正如@ljeabmreosn 提到的,50 有点太大了,我会选择 5 到 20 之间的值。

希望对您有所帮助!

对于长度小于 50 个元素的数组,带有插入排序的快速排序 "optimized" 似乎是个问题。

想象一下,我有一个大小为 65 的数组,并且枢轴恰好是该数组的中位数。如果我通过您的代码 运行 数组,您的代码将对枢轴左右两侧的两个 32 长度子数组使用插入排序。这将导致 ~O(2*(n/2)^2 + n) = ~O(n^2) 平均情况。使用快速排序并为第一个主元实现 a pivot picking strategy,时间平均情况将是 ~O((nlog(n)) + n) = ~O(n( log(n) + 1)) = ~O(n*log(n))。不要使用插入排序,因为它仅在数组几乎排序时使用。如果您使用插入排序仅仅是因为对小数组进行排序的实际 运行 宁时间可能 运行 比标准快速排序算法(深度递归)快,您始终可以使用非递归快速排序比插入排序快 运行 秒的算法。

也许把“50”改成“20”然后观察结果。