Quicksort:迭代或递归

时间:2012-09-23 14:41:01

标签: algorithm recursion quicksort iteration

我学习了快速排序以及如何在递归和迭代方法中实现它 在迭代方法中:

  1. 将范围(0 ... n)推入堆栈
  2. 使用数据透视表
  3. 对给定数组进行分区
  4. 弹出顶部元素。
  5. 如果范围包含多个元素
  6. ,则将分区(索引范围)推送到堆栈
  7. 执行上述3个步骤,直至堆栈为空
  8. 递归版本是wiki中定义的正常版本。

    我了解到递归算法总是慢于迭代算法 那么,就时间复杂度而言,哪种方法更受欢迎(内存不是问题)? 哪一个足够快,可以在编程竞赛中使用?
    c ++ STL sort()是否使用递归方法?

3 个答案:

答案 0 :(得分:23)

就(渐近)时间复杂度而言 - 它们都是相同的。

“递归慢于迭代” - 这个语句背后的理性是由于递归堆栈的开销(在调用之间保存和恢复环境)。
然而 - 这些是操作的常数,而不是改变“迭代”的数量。

递归和迭代快速排序都是O(nlogn) 平均情况O(n^2) 最坏情况


修改

只是为了它的乐趣我运行了附加到帖子的(java)代码的基准测试,然后运行wilcoxon statistic test,检查运行时间确实不同的概率

结果是决定性的(P_VALUE = 2.6e-34,这意味着它们相同的概率是2.6 * 10 ^ -34 - 非常不可能)。但答案并非你所期望的。
迭代解的平均值为408.86 ms,而递归的平均值为236.81 ms

(注意 - 我使用Integer而不是int作为recursiveQsort()的参数 - 否则递归会更好,因为它不需要包含很多整数,这也很耗时 - 我这样做是因为迭代解决方案别无选择,只能这样做。

因此 - 你的假设不正确,递归解决方案更快(对于我的机器和java至少)然后是迭代的P_VALUE = 2.6e-34。

public static void recursiveQsort(int[] arr,Integer start, Integer end) { 
    if (end - start < 2) return; //stop clause
    int p = start + ((end-start)/2);
    p = partition(arr,p,start,end);
    recursiveQsort(arr, start, p);
    recursiveQsort(arr, p+1, end);

}

public static void iterativeQsort(int[] arr) { 
    Stack<Integer> stack = new Stack<Integer>();
    stack.push(0);
    stack.push(arr.length);
    while (!stack.isEmpty()) {
        int end = stack.pop();
        int start = stack.pop();
        if (end - start < 2) continue;
        int p = start + ((end-start)/2);
        p = partition(arr,p,start,end);

        stack.push(p+1);
        stack.push(end);

        stack.push(start);
        stack.push(p);

    }
}

private static int partition(int[] arr, int p, int start, int end) {
    int l = start;
    int h = end - 2;
    int piv = arr[p];
    swap(arr,p,end-1);

    while (l < h) {
        if (arr[l] < piv) {
            l++;
        } else if (arr[h] >= piv) { 
            h--;
        } else { 
            swap(arr,l,h);
        }
    }
    int idx = h;
    if (arr[h] < piv) idx++;
    swap(arr,end-1,idx);
    return idx;
}
private static void swap(int[] arr, int i, int j) { 
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

public static void main(String... args) throws Exception {
    Random r = new Random(1);
    int SIZE = 1000000;
    int N = 100;
    int[] arr = new int[SIZE];
    int[] millisRecursive = new int[N];
    int[] millisIterative = new int[N];
    for (int t = 0; t < N; t++) { 
        for (int i = 0; i < SIZE; i++) { 
            arr[i] = r.nextInt(SIZE);
        }
        int[] tempArr = Arrays.copyOf(arr, arr.length);

        long start = System.currentTimeMillis();
        iterativeQsort(tempArr);
        millisIterative[t] = (int)(System.currentTimeMillis()-start);

        tempArr = Arrays.copyOf(arr, arr.length);

        start = System.currentTimeMillis();
        recursvieQsort(tempArr,0,arr.length);
        millisRecursive[t] = (int)(System.currentTimeMillis()-start);
    }
    int sum = 0;
    for (int x : millisRecursive) {
        System.out.println(x);
        sum += x;
    }
    System.out.println("end of recursive. AVG = " + ((double)sum)/millisRecursive.length);
    sum = 0;
    for (int x : millisIterative) {
        System.out.println(x);
        sum += x;
    }
    System.out.println("end of iterative. AVG = " + ((double)sum)/millisIterative.length);
}

答案 1 :(得分:9)

递归并不总是比迭代慢。 Quicksort就是它的完美典范。以迭代方式执行此操作的唯一方法是创建堆栈结构。因此,如果我们使用递归,那么以其他方式执行与编译器相同的操作,并且可能会比编译器更糟糕。如果你不使用递归(弹出并推送值到堆栈),也会有更多的跳转。

答案 2 :(得分:0)

这就是我用Javascript提出的解决方案。我认为可以。

function qs_iter(items) {
    if (!items || items.length <= 1) {
        return items
    }
    var stack = []
    var low = 0
    var high = items.length - 1
    stack.push([low, high])
    while (stack.length) {
        var range = stack.pop()
        low = range[0]
        high = range[1]
        if (low < high) {
            var pivot = Math.floor((low + high) / 2)
            stack.push([low, pivot])
            stack.push([pivot+1, high])
            while (low < high) {
                while (low < pivot && items[low] <= items[pivot]) low++
                while (high > pivot && items[high] > items[pivot]) high--
                if (low < high) {
                    var tmp = items[low]
                    items[low] = items[high]
                    items[high] = tmp
                }
            }
        }
    }
    return items
}

function qs_iter(items) { if (!items || items.length <= 1) { return items } var stack = [] var low = 0 var high = items.length - 1 stack.push([low, high]) while (stack.length) { var range = stack.pop() low = range[0] high = range[1] if (low < high) { var pivot = Math.floor((low + high) / 2) stack.push([low, pivot]) stack.push([pivot+1, high]) while (low < high) { while (low < pivot && items[low] <= items[pivot]) low++ while (high > pivot && items[high] > items[pivot]) high-- if (low < high) { var tmp = items[low] items[low] = items[high] items[high] = tmp } } } } return items }

如果您发现错误,请告诉我:)