迭代的快速排序(Iterative Quick Sort)

来源:互联网 发布:苹果6移动数据怎么设置 编辑:程序博客网 时间:2024/05/16 12:52

原文地址:Iterative Quick Sort
译者注:教科书中一般介绍的是递归的快速排序,当年在百度校招面试的时候被问到这个问题,没写出来,真是遗憾……

下面是一种典型的递归的实现,用最后一个元素作为枢轴(pivot)

/* A typical recursive C/C++  implementation of QuickSort *//* This function takes last element as pivot, places    the pivot element at its correct position in sorted    array, and places all smaller (smaller than pivot)   to left of pivot and all greater elements to right    of pivot */int partition (int arr[], int l, int h){    int x = arr[h];    int i = (l - 1);    for (int j = l; j <= h- 1; j++)    {        if (arr[j] <= x)        {            i++;            swap (&arr[i], &arr[j]);        }    }    swap (&arr[i + 1], &arr[h]);    return (i + 1);}/* A[] --> Array to be sorted,   l  --> Starting index,   h  --> Ending index */void quickSort(int A[], int l, int h){    if (l < h)    {                /* Partitioning index */        int p = partition(A, l, h);         quickSort(A, l, p - 1);          quickSort(A, p + 1, h);    }}

上面的实现还有很多地方可以优化。

1)以上的实现用最后一个下标作为枢轴。如果这个数组已经有序了,那么就会产生最坏的情况,这是一种很常见的情况。这个问题可以通过随机选择一个下标作为枢轴来解决,或者选择一个分区的中间位置,也可以选择分区的第一个,中间和最后一个的中值作为枢轴。(详情请见此)

2)为了减少递归的深度,首先递归数组的那少一半,用结尾调用(a tail call)递归到另外的部分。

3)用插入排序去处理小的子数组更好。插入排序可以用于这种小数组的调用(例如:长度小于门限t的时候决定实验)。例如,快速排序的这个实现用到的插入排序的大小是7.

尽管以上做了一些优化,但是函数仍然在递归而且用函数调用堆(function call stack)存储l与h的中间值。函数调用堆用参数也一起保存了其他的账目(bookkeeping)信息。函数调用也会有一些开销比如存储调用函数的激活记录,然后恢复执行。

上面的函数理由附加栈的协助可以很容易地转变为迭代版本的快速排序。下面就是上面递归代码的迭代实现。

// An iterative implementation of quick sort#include <stdio.h>// A utility function to swap two elementsvoid swap ( int* a, int* b ){    int t = *a;    *a = *b;    *b = t;}/* This function is same in both iterative and recursive*/int partition (int arr[], int l, int h){    int x = arr[h];    int i = (l - 1);    for (int j = l; j <= h- 1; j++)    {        if (arr[j] <= x)        {            i++;            swap (&arr[i], &arr[j]);        }    }    swap (&arr[i + 1], &arr[h]);    return (i + 1);}/* A[] --> Array to be sorted,    l  --> Starting index,    h  --> Ending index */void quickSortIterative (int arr[], int l, int h){    // Create an auxiliary stack    int stack[ h - l + 1 ];    // initialize top of stack    int top = -1;    // push initial values of l and h to stack    stack[ ++top ] = l;    stack[ ++top ] = h;    // Keep popping from stack while is not empty    while ( top >= 0 )    {        // Pop h and l        h = stack[ top-- ];        l = stack[ top-- ];        // Set pivot element at its correct position        // in sorted array        int p = partition( arr, l, h );        // If there are elements on left side of pivot,        // then push left side to stack        if ( p-1 > l )        {            stack[ ++top ] = l;            stack[ ++top ] = p - 1;        }        // If there are elements on right side of pivot,        // then push right side to stack        if ( p+1 < h )        {            stack[ ++top ] = p + 1;            stack[ ++top ] = h;        }    }}// A utility function to print contents of arrvoid printArr( int arr[], int n ){    int i;    for ( i = 0; i < n; ++i )        printf( "%d ", arr[i] );}// Driver program to test above functionsint main(){    int arr[] = {4, 3, 5, 2, 1, 3, 2, 3};    int n = sizeof( arr ) / sizeof( *arr );    quickSortIterative( arr, 0, n - 1 );    printArr( arr, n );    return 0;}

输出:

1 2 2 3 3 3 4 5

以上提到的对递归实现的优化也可以应用到迭代版本中。

1)两种方法分区的处理是一样的。所以相同的技术对枢轴选择的优化也可以应用到迭代的版本中。

2)首先要把元素更少的那一部分的下标push到栈中,这样可以减少stack的大小。

3)当尺寸减小到低于实验的计算门限的时候,请用插入排序。

参考文献:
http://en.wikipedia.org/wiki/Quicksort

0 0
原创粉丝点击