快速排序(Quick Sort)的递归与非递归实现

版权声明:嘤嘤嘤 https://blog.csdn.net/HNUCSEE_LJK/article/details/88365723

原理

快速排序采用分治的策略,是一个不稳定的排序算法。其基本思想是:将待排序的序列分为三个部分,分别为基准arr[p],arr[0:p]与arr[p+1:n],通过操作使得arr[0:p]中的所有元素都比基准arr[p]小,而arr[p+1:n]中的所有元素都比基准arr[p]大,然后递归地对arr[0:p]与arr[p+1:n]执行同样的操作,直到序列不可再被分割,最终将排好序的子序列合并成排好序的序列。

实现

快速排序有两种实现方式:递归的与非递归的。

递归实现

void QuickSort(int arr[], int left, int right)
{
    if(left >= right) return;
    int pos = PartSort(arr, left, right);
    QuickSort(arr, left, pos-1);
    QuickSort(arr, pos+1, right);
}

递归实现的模块一般如上,其中PartSort是进行一次快速排序的算法,有多种实现方式,这里介绍三种,分别为左右指针法挖坑法前后指针法

①左右指针法

思路:

1)选取序列中的某个元素作为基准值(基准的选取下文会有介绍),这里设基准为序列的最右一个元素

2)从左往右找到第一个小于等于基准值的数,然后再从右往左找到第一个大于等于基准值的数,交换这两个数

3)重复进行2),直到两边找到的数相同时,交换这个数与基准值的位置。此时,序列中位于基准值左边的数都比基准值小,位于基准值右边的数都比基准值大

int PartSort(int arr[], int left, int right) //左右指针法
{
    int x = left, y = right, &key = arr[right]; //key为基准值,使用引用的原因是便于交换
    while(x < y)
    {
        while(x < y && arr[x] <= key) //x<y的原因:若arr[right-1] == arr[right],则x会越界
        {
            ++x;
        }
        while(x < y && arr[y] >= key)
        {
            --y;
        }
        swap(arr[x], arr[y]);
    }
    swap(arr[x], key); //交换,然后以key作为分界,对比key小的与比key大的左右两组数进行递归排序
    return x; //返回分界点的位置
}

②挖坑法

思路:

1)选取序列中的某个元素作为基准值,作为初始的“坑”

2)从左往右找到第一个大于等于基准值的数,将它填入现有的坑中,那么这个数的位置就成为一个新的坑

3)从右往左找到第一个小于等于基准值的数,将它填入现有的坑中,那么这个数的位置就成为一个新的坑

4)反复执行2)与3),直到从右往左找到的数的位置是现有的坑,将基准值填入这个坑中

int PartSort(int arr[], int left, int right) //挖坑法
{
    int x = left, y = right, key = arr[right]; //这里的key不必引用,因为key不需要进行交换
    while(x < y)
    {
        while(x < y && arr[x] <= key)
        {
            ++x;
        }
        arr[y] = arr[x];
        while(x < y && arr[y] >= key)
        {
            --y;
        }
        arr[x] = arr[y];
    }
    arr[y] = key;
    return y;
}

③前后指针法

思路:

1)选取序列中的某个元素作为基准值

2)从左往右找到两个数,这两个数中间的所有数都比基准值大,然后交换这两个数

3)反复执行2),直到不再存在满足2)中条件的子序列,然后将从右往左第一个小于基准值的数的后一个位置的数与基准值交换位置

int PartSort(int arr[], int left, int right) //前后指针法
{
    if(left >= right) return -1;

    int pre = left-1, cur = left, &key = arr[right];
    while(cur < right)
    {
        while(arr[cur] < key && ++pre != cur)
        {
            swap(arr[pre], arr[cur]);
        }
        ++cur;
    }
    swap(arr[++pre], key);
    return pre;
}

基准值的选取

基准值的选取对于快速排序来说是至关重要的。一般而言,基准值可取序列最左的元素最右的元素,也可采用三值取中法随机法选取基准值。

int midValue(int arr[], int left, int right) //三值取中法
{
    int mid = left + ((right - left)>>1);
    if(arr[left] <= arr[right])
    {
        if(arr[mid] < arr[left]) return left;
        if(arr[mid] > arr[right]) return right;
        return mid;
    }
    else
    {
        if(arr[mid] < arr[right]) return right;
        if(arr[mid] > arr[left]) return left;
        return mid;
    }
}

int randomValue(int arr[], int left, int right) //随机法
{
    return rand()%(right-left+1) + left;
}

非递归实现

非递归实现即是用栈来模拟递归。

void QuickSort(int arr[], int left, int right)
{
    stack<int> s;
    s.push(left);
    s.push(right);
    while(!s.empty())
    {
        int right = s.top();
        s.pop();
        int left = s.top();
        s.pop();

        int pos = PartSort(arr, left, right);
        if(pos-1 > left)
        {
            s.push(left);
            s.push(pos-1);
        }
        if(pos+1 < right)
        {
            s.push(pos+1);
            s.push(right);
        }
    }
}

复杂度分析

快速排序的运行时间与基准的选取有关,即有关基准是否使得序列对称划分。

最坏情况发生在基准划分的两个子序列长度分别为1与n-1,此时

T(n)=\left\{\begin{matrix} O(1) & n\leq 1\\T(n-1) +O(n) & n >1 \end{matrix}\right.

主定理解得,T(n) = O(n^2),这是快排的复杂度上界。

在最好的情况下,每次选取的基准都使得序列恰好划分为长度相等的两个子序列,那么

T(n)=\left\{\begin{matrix} O(1) & n\leq 1\\ 2T(n/2) +O(n) & n >1 \end{matrix}\right.

解得T(n) = O(nlogn)。

而在平均情况下,可以证明快排的算法复杂度同样为O(nlogn)。

参考文章:

快速排序(三种算法实现和非递归实现)

《计算机算法设计与分析》 王晓东

猜你喜欢

转载自blog.csdn.net/HNUCSEE_LJK/article/details/88365723