快速排序QuickSort

目录

1.Hoare法

2.挖坑法

3.前后指针法

4.快排分治

5.关于快排

 6.关于快排的优化

7.总体实现

总结:


快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法

其基本思想为:任待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。(分治思想)

概括快排的思想:
找一个key(基准值),通过交换,使key的左边全小于key,右边全部大于key。这样key就在正确的位置上。重复该过程,直到所有的值都在正确位置上,完成排序。

对比选择排序、插入排序,每次调整无非就第一个元素和最后一个元素在正确位置上。快速排序的单趟排序,可以将调整中间元素,类似一个二叉树的结构。

快排具有高效和广泛的使用,它是最经典的排序之一,并且在各种编程语言的算法库中被广泛使用。

1.Hoare法

Hoare法就是快速排序的创始人Hoare最初的方法,是目前快排中学习成本最高的方法,让我来带大家领略Hoare大佬的思想吧。


 

单趟排序思想:

左边或者右边第一个位置当作key的位置,本文以左边为例。

双指针Left和Right。Right先走,Right找小,找到了停下。Left再走,Left找大,找到了停下,交换Left和Right的值。Right再走找小,Left找到,交换......直到二者相遇,交换key和相遇点的值。这样的一趟排序后key就在正确的位置上。

下图解释单趟排序的过程:

本趟排序,使左边的元素都比key小,右边的元素都比key大。从而使key在正确的位置上。

单趟排序的实现:

// 快速排序hoare版本
int PartSort1(int* a, int left, int right)
{
    int keyi = left;
    while (left < right)
    {
        while (left < right && a[right] >= a[keyi])
        {
            right--;
        }
        while (left < right && a[left] <= a[keyi])
        {
            left++;
        }
        Sweap(&a[left], &a[right]);
    }
    Sweap(&a[keyi], &a[left]);
    keyi = left;
    return keyi;
}

 关于单趟排序的俩个问题:

1.a[right]一定要>=a[key]?a[right]>a[keyi]行吗?

不行。当出现L 和 R同时遇到与K相同的元素时,会出现死循环。

 2.如果没有越界条件left<right

会出现栈溢出。如果出现极端情况下,key是最小的数,R就会一直减,出现栈溢出。

Hoare法的快速排序是坑比较多,但是他的思想是非常的高超。领略Hoare的方法,在理解代码起巨大的作用。

2.挖坑法

找一个key为坑,假定左边第一个元素为坑,R.L双指针指向左右,R找小,把坑的值填给R中,R形成新坑,L找大,直到L R相遇,把key值给坑。key就在正确位置上

下面动图展示:

 由于挖坑法的思路较简单,就不画图演示。

下面展示挖坑法的单趟实现:

// 快速排序挖坑法
int PartSort2(int* a, int left, int right)
{
    int key = a[left];
    int hole = left;
    while (left < right)
    {
        while (left < right && a[right] >= key)
        {
            right--;
        }
        a[hole] = a[right];
        hole = right;

        while (left < right && a[left] <= key)
        {
            left++;
        }
        a[hole] = a[left];
        hole = left;
    }
    a[hole] = key;
    return hole;
}

 

 挖坑法与Hoare的思想差不多,二者殊途同归。

3.前后指针法

前后指针法的思路:定义key prev cur指针

cur找小,找到了++prev 交换prev和cur的值。cur再走,在交换,直到cur>=n,交换prev和key的值

下面为动图演示:

单趟排序画图详解:

 从图中可以看出,将大的元素翻滚式的推到后面。使左边的值全部小于key,右边的值全部大于key。从而key在正确的位置上。

前后指针法单趟排序实现:

// 快速排序前后指针法
int PartSort3(int* a, int left, int right)
{
    int keyi = left;
    int prev = left;
    int cur = left + 1;
    while (cur <= right)
    {
        if (a[cur] < a[keyi] && ++prev != cur)
        {
            Sweap(&a[cur], &a[prev]);
        }
        ++cur;
    }
    Sweap(&a[keyi], &a[prev]);
    keyi = prev;
    return keyi;
}

前后指针法的实现较为简单,如果能熟练掌握前俩种方法,那么第三种方法便游刃有余。

关于Hoare法的思考:

L 和 R相遇点,会比key大吗?

  不会。相遇的值一定小于key。

1)假设是key在左边,R去遇L,L不动,存在俩种情况 

1.L一直没动,就在key的位置 ,可以直接交换

2.L经过上一轮交换后,已经比key小,相遇后交换必定比key小,可以直接交换

.

2)L去遇R,由于每一轮都是R先走,R找小 找到停下,L去遇R,相遇点就是R停下的位置,一定比key小,可以直接交换。

4.快排分治

每一趟排序后,key在正确位置上,即将数组分成[Left,keyi-1]  keyi  [keyi+1,Right]

后续就要对[Left,keyi-1]  和 [keyi+1,Right]排序即可。

void QuickSort(int* a, int left, int right)
{
    if (left >= right)
        return;

    int keyi = PartSort2(a, left, right);
    QuickSort(a, left, keyi - 1);
    QuickSort(a, keyi + 1, right);

}

类似二叉树的前序遍历结构

当最小分割只有一个元素时,(即条件left>=right )停止递归。

5.关于快排

1)时间复杂度

单趟排序:类似满二叉树,树的高度为logN  

整体排序:对N个元素排序,N

总的时间复杂度O(N)=N*log(N)

2)空间复杂度:

最大二叉树的叶子结点为N 空间复杂度为N

3)稳定性

不稳定。快排是通过交换 

例如  3  3  4   2  6  6  单趟排序完 俩个3的次序会发生改变

快排是不稳定的算法。

 6.关于快排的优化

如果key为最小的元素,那么每趟排序都要经过N次 总的时间复杂度为N^2

引入三数取中,找到mid left right 中间大的元素,交换后 以它做key

int GetMidIndex(int *a,int left, int right)
{
    int mid = (left + right) / 2;
    if (a[left] > a[mid])
    {
        if (a[mid] > a[right])
            return mid;
        else if (a[right] > a[left])
            return left;
        else
            return right;
    }
    else //a[left]<a[mid]
    {
        if (a[mid] < a[right])
            return mid;
        else if (a[right] < a[left])
            return left;
        else
            return right;
    }
}

7.总体实现

void Sweap(int* a, int* b)
{
    int tmp = *a;
    *a = *b;
    *b = tmp;
}



int GetMidIndex(int *a,int left, int right)
{
    int mid = (left + right) / 2;
    if (a[left] > a[mid])
    {
        if (a[mid] > a[right])
            return mid;
        else if (a[right] > a[left])
            return left;
        else
            return right;
    }
    else //a[left]<a[mid]
    {
        if (a[mid] < a[right])
            return mid;
        else if (a[right] < a[left])
            return left;
        else
            return right;
    }
}

// 快速排序递归实现
// 快速排序hoare版本
int PartSort1(int* a, int left, int right)
{
    int mid = GetMidIndex(a, left, right);
    Sweap(&a[left], &a[mid]);
    
    int keyi = left;
    while (left < right)
    {
        while (left < right && a[right] >= a[keyi])
        {
            right--;
        }
        while (left < right && a[left] <= a[keyi])
        {
            left++;
        }
        Sweap(&a[left], &a[right]);
    }
    Sweap(&a[keyi], &a[left]);
    keyi = left;
    return keyi;
}
// 快速排序挖坑法
int PartSort2(int* a, int left, int right)
{
    int mid = GetMidIndex(a, left, right);
    Sweap(&a[left], &a[mid]);
    int key=a[left];
    int hole = left;
    while (left < right)
    {
        while (left < right && a[right] >= key)
        {
            right--;
        }
        a[hole] = a[right];
        hole = right;

        while (left < right && a[left] <= key)
        {
            left++;
        }
        a[hole] = a[left];
        hole = left;
    }
    a[hole] = key;
    return hole;
}
// 快速排序前后指针法
int PartSort3(int* a, int left, int right)
{
    int mid = GetMidIndex(a, left, right);
    Sweap(&a[left], &a[mid]);
    int keyi = left;
    int prev = left;
    int cur = left + 1;
    while (cur <= right)
    {
        if (a[cur] < a[keyi]&&++prev!=cur)
        {
            Sweap(&a[cur], &a[prev]);
        }
        ++cur;
    }
    Sweap(&a[keyi], &a[prev]);
    keyi = prev;
    return keyi;
}


void QuickSort(int* a, int left, int right)
{
    if (left >= right)
        return;

    int keyi = PartSort2(a, left, right);
    QuickSort(a, left, keyi - 1);
    QuickSort(a, keyi + 1, right);

}

总结:

关于快排的书写,重点是画好图,多对单趟排序模拟。

Hoare法的难度较大,要多留意。在面试时选择题经常会出现快排的思想。

作者水平有限,如有问题,欢迎探讨。

  • 46
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 40
    评论
评论 40
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深度搜索

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值