50

我已经实现了以下快速排序算法。网上我读到它有 O(log(n)) 的空间要求。为什么会这样?我没有创建任何额外的数据结构。

是因为我的递归会在堆栈上使用一些额外的空间吗?如果是这种情况,是否可以通过不递归(而不是使其迭代)来减少内存?

private static void quickSort (int[] array, int left, int right) {
    int index = partition(array, left, right);

    //Sort left half
    if (left < index - 1)
        quickSort(array, left, index - 1);

    //Sort right half
    if (index < right)
        quickSort(array, index , right);
}

private static int partition (int array[], int left, int right) {
    int pivot = array[(left + right) / 2]; //Pick pivot point
    while (left <= right) {
        //Find element on left that should be on right
        while (array[left] < pivot)
            left++;

        //Find element on right that should be on left
        while (array[right] > pivot)
            right--;

        //Swap elements and move left and right indices
        if (left <= right) {
            int temp = array[left];
            array[left] = array[right];
            array[right] = temp;
            left++;
            right--;
        }
    }
    return left;
}
4

6 回答 6

61

正确,额外的空间是 log(n) 堆栈帧。来自Quicksort 的维基百科文章

有一个更复杂的版本,[...] 可以平均使用 O(log n) 空间(不计算输入)(对于调用堆栈)实现完整排序。

虽然您可以迭代地实现快速排序(即,使用循环而不是递归),但您需要维护一个辅助堆栈,因为快速排序有两个递归调用,而不仅仅是一个。

最后,正如其他答案所指出的那样,O(log(n)) 对于几乎所有实际应用来说都是非常非常小的。每个常量因素,比如数据结构的开销,都会对内存使用产生更大的影响。

于 2012-09-24T21:47:13.297 回答
11

要摆脱递归调用,您必须在代码中使用堆栈数据结构,它仍然会占用log(n)空间。

于 2012-09-24T21:54:57.747 回答
9

如果您进一步阅读 Wikipedia 文章,您会发现对空间复杂度的更彻底的讨论。特别是,他们写道:

在进行任何递归调用之前,具有就地和不稳定分区的快速排序仅使用恒定的额外空间。快速排序必须为每个嵌套递归调用存储恒定数量的信息。由于最好的情况最多进行 O(log n) 嵌套递归调用,因此它使用 O(log n) 空间。然而,如果没有 Sedgewick 限制递归调用的技巧,在最坏的情况下,快速排序可能会进行 O(n) 嵌套递归调用并需要 O(n) 辅助空间。

实际上,O(log n) 内存什么都不是。例如,如果要对 10 亿个整数进行排序,存储它们需要 4 GB,但堆栈只需要大约 30 个堆栈帧,大约 40 个字节,因此总共大约 1200 个字节。

于 2012-09-24T22:00:59.690 回答
2

是的,这是因为堆栈帧,是的,可以通过做一些非常聪明的事情将其转换为迭代算法(尽管我没有立即得到任何东西)。但为什么?O(log(n)) 空间几乎没有。作为参考,即使你有一个 Java 允许的最大大小的数组,也就是 2^31 个元素,大约是 8 GB。快速排序需要 31 个堆栈帧。Ballpark,也许每帧 100 字节?所以总共 3 KB,与实际数组的内存相比,这不算什么。

实际上,几乎任何时候都有 O(log(n)),它几乎与常数相同。

于 2012-09-24T21:55:16.697 回答
2

很抱歉重新提出这个老问题,但我刚刚在planetmath.org上找到了一个完全不同(但有点愚蠢)的答案:

任何在连续数组上操作的排序算法都需要O ⁢ (log ⁡n ) 额外空间,因为这是表示数组索引所需的比特数 [ sic ]。

于 2014-01-16T15:52:37.647 回答
0

每次连续递归调用时,子列表的大小减半,当子列表为 1 时,递归终止。因此,对元素进行操作的次数等于在达到 1 之前可以将 n 除以 2 的次数;日志(n)。

于 2021-11-13T20:27:55.177 回答