快速排序和随机快速排序

原创作品 转载请注明出处http://blog.csdn.net/always2015/article/details/46523531

一、问题描述

实现对数组的普通快速排序与随机快速排序
(1)实现上述两个算法
(2)统计算法的运行时间
(3)分析性能差异,作出总结

二、算法原理:

1、快速排序

快速排序是对冒泡排序的一种改进。它的基本思想是:选取一个基准元素,通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比基准元素小,另外一部分的所有数据都要比基准元素大,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
设要排序的数组是A[0]……A[N-1],首先选取一个数据(普通快速排序选择的是第一或者最后一个元素, 随机快速排序是随机选择一个元素)作为关键数据,然后将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一趟快速排序。
一趟快速排序的算法是:
1)设置两个变量i、j,排序开始的时候:i=0,j=N-1;
2)以第一个数组元素作为关键数据,赋值给key,即key=A[0];
3)从j开始向前搜索,即由后开始向前搜索(j–),找到第一个小于key的值A[j],将A[j]赋给A[i];
4)从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]赋给A[j];
5)重复第3、4步,直到i=j; (3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-完成的时候,此时令循环结束)。
本算法实现用到随机的500个数据进行试验,随机产生的数据如下图:
这里写图片描述

经过快速排序后结果如下:

这里写图片描述

快速排序代码如下:

#include <iostream>
#include<time.h>
#include<stdlib.h>

#define NUMBER 500//产生待排随机数的个数
using namespace std;

void QuickSort(int *A,int l,int r)
{
    int temp;
    int i=l,j=r;
    if(l<r)
    {
        temp=A[l];
        while(i!=j)
        {
            while(j>i&&A[j]>temp) --j;//从右边扫描找到一个小于temp元素

            if(i<j)
            {
                A[i]=A[j];
                ++i;
            }
            while(i<j&&A[i]<temp) ++i;//从左边扫描,找到一个大于temp元素

            if(i<j)
            {
                A[j]=A[i];
                --j;
            }
        }
        A[i]=temp;//temp放在最终位置
        QuickSort(A,l,i-1);//对temp左边元素进行扫描
        QuickSort(A,i+1,r);//对temp右边元素进行扫描
    }
}

int main(void)
{
    srand((unsigned)time(NULL));
    clock_t begin_time,end_time;
    int *p;

    p=new int[NUMBER];

    for(int k=0;k<NUMBER;k++)
    {
        p[k]=rand()%NUMBER;
        cout << p[k]<<" ";
    }
    cout<<endl<<endl;

    begin_time=clock();
    QuickSort(p,0,NUMBER-1);
    end_time=clock();

    for(int i=0;i<NUMBER;i++)
    {
        cout<<p[i]<<" ";
    }

    cout<<endl<<endl<<end_time-begin_time<<" ms"<<endl;
    return 0;
}

2、随机快速排序

快速排序的最坏情况基于每次划分对主元的选择。基本的快速排序选取第一个或者最后一个元素作为主元。这样在数组已经有序的情况下,每次划分将得到最坏的结果。一种比较常见的优化方法是随机化算法,即随机选取一个元素作为主元。这种情况下虽然最坏情况仍然是O(n^2),但最坏情况不再依赖于输入数据,而是由于随机函数取值不佳。实际上,随机化快速排序得到理论最坏情况的可能性仅为1/(2^n)。所以随机化快速排序可以对于绝大多数输入数据达到O(nlogn)的期望时间复杂度。
贴上随机快速排序随机产生的数字,然后再进行排序,图如下,然后在对代码进行分析:
这里写图片描述

随机快速排序的结果如下:
这里写图片描述

随机快速排序和快速排序的差别就是在选取主元(枢轴)的方式不一样而已,快速排序一般选取第一个或者最后一个,随机快速排序则是随机选取的主元。为了方便,我下面的代码随机选取主元后,把主元放到最后一个位置,然后再进行排序,代码如下:

#include <iostream>
#include<stdlib.h>
#include<time.h>

#define NUMBER 500
using namespace std;

//两数交换
void exchange(int &a,int &b)
{
    int temp;
    temp=a;
    a=b;
    b=temp;
}
//p表示将数组A排序的起始下标,r是结束下标
int random_patition(int *A,int p,int r)
{
    int temp;
    int i=p-1;
    //产生随机数组下标
    int k= p + rand()%(r -p +1);
    //仍然将随机的枢轴交换到最后
    exchange(A[r],A[k]);
    temp=A[r];

    for(int j=p;j<=r-1;j++)
    {
        if(A[j]<=temp)
        {
            i=i+1;
            exchange(A[i],A[j]);
        }
    }
    //最后主元交换
    exchange(A[i+1],A[r]);
    return i+1;
}

//递归调用
void QuickSort(int *A,int p,int q)
{
    if(p<q){
        int r = random_patition(A, p, q);
        QuickSort(A, p, r-1);
        QuickSort(A, r+1, q);
    }

}

int main(void)
{
    srand((unsigned)time(NULL));
    clock_t begin_time,end_time;
    int *p;
    p=new int[NUMBER];

    for(int k=0;k<NUMBER;k++)
    {
        p[k]=rand()%NUMBER;
        cout << p[k]<<" ";
    }
    cout<<endl<<endl;

    begin_time=clock();
    QuickSort(p,0,NUMBER-1);
    end_time=clock();

    for(int i=0;i<NUMBER;i++)
    {
        cout<<p[i]<<" ";
    }

    cout<<endl<<endl<<end_time-begin_time<<" ms"<<endl;

    return 0;
}

我的上述代码排序方式稍微和快速排序有点差别,但是都是就地排序。

三、结果分析

快速排序的平均时间复杂度为O(nlogn),最坏时间时间可达到O(n^2),最坏情况是当要排序的数列基本有序的时候。根据快速排序的工作原理我们知道,选取第一个或者最后一个元素作为主元,快速排序依次将数列分成两个数列,然后递归将分开的数列进行排序。当把数列平均分成两个等长的数列时,效率是最高的,如果相差太大,效率就会降低。
我们通过使用随机化的快速排序随机的从数列中选取一个元素与第一个,或者最后一个元素交换,这样就会使得数列有序的概率降低。所以随机快速排序平均速度是比快速排序要快的。

随机化快速排序的平均时间复杂度为O(nlogn),最坏时间时间也会达到O(n^2),这种机率已经降得很小。

### 快速排序随机快速排序的区别 #### 1. 定义与核心思想 快速排序是一种基于分治法的高效排序算法,其主要思想是通过选取一个基准值(pivot),将数组划分为两部分:一部分小于等于基准值,另一部分大于基准值,然后递归地对这两部分进行排序[^2]。 随机快速排序是对传统快速排序的一种改进版本。它的核心在于每次划分时,不再固定选择第一个元素作为基准值,而是从当前子数组中随机选择一个元素作为基准值[^5]。 --- #### 2. 时间复杂度分析 两种算法的时间复杂度理论上是一致的,在理想情况下均为 \(O(n \log n)\),但在最坏情况下的时间复杂度为 \(O(n^2)\)[^3]。然而,由于随机快速排序在基准值的选择上引入了随机性,因此能够有效降低遇到最坏情况的概率,从而提高整体性能。 以下是两种算法的时间复杂度总结: - **最佳情况**: \(O(n \log n)\) - **平均情况**: \(O(n \log n)\) - **最差情况**: - 快速排序: 当输入数组已经有序或接近有序时,容易退化到 \(O(n^2)\)[^4]。 - 随机快速排序: 即使输入数组有序,随机化的基准值选择也能显著减少退化概率[^5]。 --- #### 3. 实现方式对比 ##### (1) 快速排序的经典实现 经典快速排序通常默认取数组的第一个元素作为基准值。其实现逻辑如下: ```python def quick_sort(arr): if len(arr) <= 1: return arr else: pivot = arr[0] # 默认选第一个元素为基准值 less_than_pivot = [x for x in arr[1:] if x <= pivot] greater_than_pivot = [x for x in arr[1:] if x > pivot] return quick_sort(less_than_pivot) + [pivot] + quick_sort(greater_than_pivot) ``` 此实现在处理已排序或近似已排序的数据时表现较差,因为每次分割都会导致一边为空集合,另一边几乎包含所有剩余元素[^2]。 --- ##### (2) 随机快速排序的实现 为了克服上述缺陷,随机快速排序会在每次分区前,从当前子数组中随机挑选一个元素作为基准值。这可以通过交换操作来实现: ```python import random def randomized_quick_sort(arr): if len(arr) <= 1: return arr else: # 随机选择基准值并将其移到第一位 pivot_index = random.randint(0, len(arr)-1) arr[0], arr[pivot_index] = arr[pivot_index], arr[0] pivot = arr[0] less_than_pivot = [x for x in arr[1:] if x <= pivot] greater_than_pivot = [x for x in arr[1:] if x > pivot] return randomized_quick_sort(less_than_pivot) + [pivot] + randomized_quick_sort(greater_than_pivot) ``` 这种做法使得即使面对极端分布的数据集,也很难触发最坏情况[^5]。 --- #### 4. 性能差异的实际影响 尽管两者理论上的渐进复杂度一致,但由于实际运行环境中的各种因素(如缓存命中率、分支预测准确性等),随机快速排序往往表现出更优的整体性能。特别是在大数据量场景下,随机化策略有助于缓解因数据特性引起的性能波动[^4]。 --- ### 结论 综上所述,快速排序随机快速排序的主要区别体现在基准值的选择机制上。前者采用固定的首个元素作为基准值,后者则利用随机数生成器动态决定基准值的位置。这一改动虽然看似简单,却极大地提升了算法应对特殊输入模式的能力,使其更加鲁棒且适用于广泛的应用场合。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值