快速排序

一、快速排序的介绍

快速排序是一种排序算法,对包含n个数的输入数组,最坏的情况运行时间为Θ(n2)[Θ 读作theta]。虽然这个最坏情况的运行时间比较差,但快速排序通常是用于排序的最佳的实用选择。

这是因为其平均情况下的性能相当好:期望的运行时间为 Θ(nlgn),且Θ(nlgn)记号中隐含的常数因子很小。另外,它还能够进行就地排序,在虚拟内存环境中也能很好的工作。

和归并排序一样,快速排序也是基于分治法(Divide and conquer):

  • 分解:数组A[p..r]被划分成两个(可能为空)的子数组A[p..q-1]和A[q+1..r],使得A[p..q-1]中的每个元素都小于等于A[q],A[q+1..r]中的每个元素都大于等于A[q]。这样元素A[q]就位于其最终位置上了。

  • 解决:通过递归调用快速排序,对子数组A[p..q-1]和A[q+1..r]排序。

  • 合并:因为两个子数组是就地排序,不需要合并,整个数组已有序。

伪代码:

PARTITION(A, p, r)
    x = A[p]
    i = p
    for j=p+1 to r
        do if A[j] <= x
            then i = i+1
                 exchange(A[i],A[j])
    exchange(A[p], A[i])
    return i

QUICKSORT(A, p, r)
    if p < r
        then q = PARTITION(A, p, r)
             QUICKSORT(A, p, q-1)
             QUICKSORT(A, q+1, r)

二、性能分析

1、最坏情况

快速排序的最坏情况发生在当数组已经有序或者逆序排好的情况下。这样的话划分过程产生的两个区域中有一个没有元素,另一个包含n-1个元素。

此时算法的运行时间可以递归地表示为:T(n) = T(n-1)+T(0)+Θ(n),递归式的解为T(n)=Θ(n^2)。可以看出,快速排序算法最坏情况运行时间并不比插入排序的更好。

2、最好情况

如果我们足够幸运,在每次划分操作中做到最平衡的划分,即将数组划分为n/2:n/2。此时得到的递归式为T(n) = 2T(n/2)+Θ(n),根据主定理的情况二可得T(n)=Θ(nlgn)。

3、平均情况

假设一:快排中的划分点非常偏斜,比如每次都将数组划分为1/10 : 9/10的两个子区域,这种情况下运行时间是多少呢?运行时间递归式为T(n) = T(n/10)+T(9n/10)+Θ(n),使用递归树解得T(n)=Θ(nlgn)。可以看出,当划分点非常偏斜的时候,运行时间仍然是Θ(nlgn)。
**假设二:**Partition所产生的划分既有“好的”,也有“差的”,它们交替出现。这种平均情况下运行时间又是多少呢?这时的递归式为(G表示Good,B表示Bad):

G(n) = 2B(n/2) + Θ(n) B(n) = G(n-1) + Θ(n)
解:G(n) = 2(G(n/2-1) +Θ(n/2)) + Θ(n) = 2G(n/2-1) + Θ(n) = Θ(nlgn)

可以看出,当好、差划分交替出现时,快排的运行时间就如全是好的划分一样,仍然是Θ(nlgn) 。

三、快排的优化

经过上面的分析可以知道,在输入有序或逆序时快速排序很慢,在其余情况则表现良好。如果输入本身已被排序,那么就糟了。

那么我们如何确保对于所有输 入,它均能够获得较好的平均情况性能呢?前面的快速排序我们默认使用数组中第一个元素作为主元。假设随机选择数组中的元素作为主元,则快排的运行时间将不 依赖于输入序列的顺序。我们把随机选择主元的快速排序叫做Randomized Quicksort。

在随机化的快速排序中,我们不是始终选择第一个元素作为主元,而是从数组A[p…r]中随机选择一个元素,然后将其与第一个元素交换。由于主元元素是随机选择的,我们期望在平均情况下,对输入数组的划分能够比较对称。

伪代码:

RANDOMIZED-PARTITION(A, p, r)
    i = RANDOM(p, r)
    exchange(A[p], A[i])
    return PARTITION(A, p, r)

RANDOMIZED-QUICKSORT(A, p, r)
    if p < r
        then q = RANDOMIZED-PARTITION(A, p, r)
            RANDOMIZED-QUICKSORT(A, p, q-1)
            RANDOMIZED-QUICKSORT(A, q+1, r)

传统的快速排序 和 随机化的快速排序完整代码:

#include <iostream>
#include<cstdlib> 
#include<ctime> 

using namespace std;


void Swap(int &a,int &b)
{
    int temp = a;
    a = b;
    b = temp;
}

int Partition(int arr[], int begin, int end)
{
    int pivot = arr[begin];
    int i = begin ;
    for (int j = begin + 1; j <= end; ++j)
        if (arr[j] <= pivot)
        {
            ++i;
            Swap(arr[i], arr[j]);
        }

    Swap(arr[i], arr[begin]);

    return i;
}

int Rand_Partition(int arr[], int begin, int end)
{
    srand(time(NULL));   //根据系统时间设置随机数种子
    int i = rand() % (end - begin + 1) + begin;   //取得区间[begin,end+1)的整数

    Swap(arr[i], arr[begin]);

    return Partition(arr, begin, end);

}

void QuickSort(int arr[], int begin, int end)
{
    if (begin < end)
    {
        int index = Partition(arr, begin, end);

        QuickSort(arr, begin, index - 1);
        QuickSort(arr, index + 1, end);

    }

}

void Rand_QuickSort(int arr[], int begin, int end)
{
    if (begin < end)
    {
        int q = Rand_Partition(arr, begin, end);

        Rand_QuickSort(arr, begin, q - 1);
        Rand_QuickSort(arr, q + 1, end);

    }
}



int main()
{
    int a[] = { 12, 23, 34, 45, 11, 32, 9, 22, 55 };

    QuickSort(a, 0, 8);

    cout << "普通的快速排序:";
    for (int i = 0; i <= 8; i++)
        cout << a[i] << " ";
    cout << endl;

    int b[] = { 12, 23, 34, 45, 11, 32, 9, 22, 55, 33, 57, 90 };
    Rand_QuickSort(b, 0, 11);

    cout << "随机化的快速排序:";
    for (int i = 0; i <= 11; i++)
        cout << b[i] << " ";
    cout << endl;

    system("pause");
    return 0;
}

版权声明:本文为博主原创文章,未经博主允许不得转载。

posted @ 2015-09-14 20:42  taxue505  阅读(166)  评论(0编辑  收藏  举报