快速排序入门(动图演示)

快速排序时间复杂度是O(nlogn)

快速排序思路

快速排序入门(动图演示)_第1张图片
(整个图大概有六七分钟,完整的演示了快速排序的全部内容)

下面给出一次快速排序的思路:

排序主要思想就是twoPointers 思想(twoPointers是什么?)

①定义两个变量 i 和 j分别指向待排序数组的首尾,定义一个临时变量temp存储 i 指向的元素内容(一次排序目的就是令temp在序列中寻找一个合适的位置,使temp左侧元素都不超过temp,右侧的元素都大于temp)
②令 j 不断向左移动,直到找到某一元素A[ j ] <= 该元素,然后将它挪到A[ i ]所在位置(A[ i ]处的元素已经保存在了temp中),j 进入等待状态, 进入③
③令 i 不断向右移动, 直到某一元素值A[ i ] > 该元素, 将A[ i ]挪到A[ j ]所在位置(原来的元素已经挪到了该去的地方), i 进入等待状态, 进入②
④ ②③不断循环,当i 和 j 指向同一个位置时该位置就是temp应该插入的位置,直接插入就行了(原来的元素已经挪到了该去的地方)

好了,现在temp左侧的元素一定不大于它,temp右侧的元素一定大于它,等于说当整个数组全部排好序后temp的位置就在这里

下面进行[ 0, temp-1 ] 和 [temp+1, n-1]位置元素的排序,方法和上面的相同,然后再不断的切割不断地排序,直到某一区间仅有一个元素为止,这就要用到递归了

下面给出一次排序的代码

int Partition(int A[], int left, int right)
{
    int temp = A[left];
    while(left < right)
    {
        while(temp < A[right] && left < right) right--;
        A[left] = A[right];
        while(temp >= A[left] && left < right) left++;
        A[right] = A[left];
    }
    A[left] = temp;
    return left;
}

有了一次排序后我们就可以递归进行多次排序

void QuickSOrt(int A[], int left, int right)
{
    if(left < right)
    {
        int pos = Partition(A, left, right);
        QuickSOrt(A, left, pos-1);
        QuickSOrt(A, pos+1, right);
    }
}

下面是完整代码

#include 
#include
using namespace std;

int Partition(int A[], int left, int right)
{
    int temp = A[left];
    while(left < right)
    {
        while(temp < A[right] && left < right) right--;
        A[left] = A[right];
        while(temp >= A[left] && left < right) left++;
        A[right] = A[left];
    }
    A[left] = temp;
    return left;
}

void QuickSOrt(int A[], int left, int right)
{
    if(left < right)
    {
        int pos = Partition(A, left, right);
        QuickSOrt(A, left, pos-1);
        QuickSOrt(A, pos+1, right);
    }

}

int main()
{
    int A[11] = {35,18,16,72,24,65,12,88,46,28,55};
    QuickSOrt(A, 0, 10);
    for(int i = 0; i< 11; i++)
    {
        printf("%d ", A[i]);
    }
    return 0;
}

输出:
快速排序入门(动图演示)_第2张图片

快速排序优化

快速排序的特点是元素的排列越随机(不按顺序的程度高)效率越高,但序列中元素接近有序时会达到最坏的时间复杂度O(n2),原因在于我们总令temp是待排区间最左侧的元素,比如1 2 3 5 4,我们取到了1,它左边没区间,右边区间只减少了1
优化的具体做法是尽可能的令temp在区间内随机取值,这样,该算法的数学期望就稳定在O(nlogn) (详细证明见《算法导论》)

c语言中有个随机数生成器,它的写法具体是这样的:

#include
#include
#include
...
void _rand()
{
    srand((unsigned) time(NULL)); //随机数生成器必备语句
    for(int i = 0; i< 10; i++)
        printf("%d  ", rand());  //rand()生成随机数
    printf("\nRAND_MAX = %d", RAND_MAX);  // 生成最大值
}

输出结果
快速排序入门(动图演示)_第3张图片
它输出最大值为32767,如果我们想令它生成范围在[a , b]之间该怎么办呢?

rand() % (b-a+1) => 输出区间在[0, b-a]之间,再 +a 就是区间[a , b]了, 即

rand() % (b-a +1) + a

如果想生成大于32767的区间该怎么办呢?
一种方法是:rand() / RAND_MAX 生成[0 , 1]的浮点数,再乘(b-a)+ a即可

(int)(round(1.0*rand() / RAND_MAX * (b-a)+a))

回到快速排序上
我们要做的是令待排序元素temp在[ i, j ]中随机取,不要忘记,已经排序好的元素时不会出现在区间内的,所以我们取一个随机元素A[p], 让它和A[ i ] 交换即可,没有任何影响。而且temp变量还可以 = A[ i ],前面的代码不会发生变化


int randPartition(int A[], int left, int right)
{
	//变化开始处
    srand((unsigned)time(NULL));
    int p = 1.0*rand() / RAND_MAX*(right-left) +left;
    swap(A[left], A[p]);
    //变化终止处
    int temp = A[left];
    while(left < right)
    {
        while(temp < A[right] && left < right) right--;
        A[left] = A[right];
        while(temp >= A[left] && left < right) left++;
        A[right] = A[left];
    }
    A[left] = temp;
    return left;
}

快速排序入门(动图演示)_第4张图片
输出结果相同
over

你可能感兴趣的:(个人经验)