C++版本快速排序

总体思想

先找到一个枢轴,让他作为分水岭。通过一趟排序将待排序的记录分割成独立的两部分,前面一部分都比枢轴小,后面一部分逗比枢轴大,然后又分别对这两部分记录继续进行递归的排序,达到整个序列有序的目的。

核心分离算法

如何根据枢轴将无序序列分成两个独立的部分是快速排序的关键,具体做法是:

  1. 采用两个辅助变量,一个指向待排序序列的第一个元素(low),另一个指向最后一个元素(high),这里的low和high都是待排序序列的下标或者称之为元素在序列中的位置;
  2. 然后先从high开始,比较后面的元素和枢轴的大小,如果比枢轴大,则不移动元素,将high–,使得high往前移动;直到high指向的元素比枢轴小,high停止往前移动,交换枢轴和high指向元素的位置;
  3. 从low开始,比较前面的元素和枢轴的大小,如果比枢轴小,则不移动元素,将low++,使得low往后移动;直到low指向的元素比枢轴大,low停止往前移动,交换枢轴和low指向元素的位置;
  4. 进行下一次循环,又从high开始进行比较,直到low不小于high为止,退出循环,此时的序列则按照枢轴一分为二,达到我们的目的。

传统的快速排序代码

template <typename T>
void SqList<T>::QSOrt(int low, int high)
{
    int pivot_Position;
    if (low < high)
    {
        pivot_Position = Partition(low,high);
        QSOrt(low, pivot_Position - 1);
        QSOrt(pivot_Position + 1, high);
    }
}

template <typename T>
int SqList<T>::Partition(int low, int high)
{
    T pivot_Var = r[low];
    while (low < high)
    {
        while (r[high] >= pivot_Var && low < high )
        {
            high--;
        }
        swapElement(low, high);

        while (r[low] <= pivot_Var && low < high)
        {
            low++;
        }
        swapElement(low, high);
    }
    return low;
}

枢轴的选取

一般采取“三个元素取中间”的做法,即从要排序的序列中,取出左、中、右三个元素,然后选取他们三个中的中间值作为比较的枢轴。一般来说,传统的快速排序是将中间值和low交换,使得在开始排序之前low位置处存放的就是枢轴。方便后面的比较和交换操作。但这里需要改进!

避免不必要的交换

上面提到了枢轴如果一开始存放到low处,后面再进行和序列中其他元素交换需要改进,毕竟交换操作太频繁是很低效的。所以能避免交换当然要避免,根据快速排序的思想,在发现后面的元素比枢轴小或者前面的元素比枢轴大的时候,需要交换枢轴和相应元素,其实这里只需要进行覆盖就可以,分析如下:

  1. 从high开始往前走,和枢轴比较的时候,low位置存放的是枢轴(一开始枢轴就放在low处),如果遇到比枢轴小的元素,进行交换,也就是low处存放原来high处的元素,而high处存放low处的元素;
  2. 从low开始往后走,和枢轴比较的时候,high位置存放的是枢轴(经过前面的操作,high存放的就是枢轴),如果遇到比枢轴大的元素,进行交换,也就是low处存放原来high处的元素,而high处存放low处的元素;
  3. 这样看来,其他元素实际上在一趟排序过程中只交换了一次,是合理的,但是枢轴这个元素则交换了很多次,但最终枢轴应该只存放在它应该处于的位置,而这个位置就是最后循环结束以后,low代表的位置。因为经过high–和low++以后,从两边向中间靠拢的过程中,就能确定选取的枢轴应该在这个序列中的位置。这也正是快速排序算法精髓的所在。所以可以在需要交换枢轴和其他元素的时候,直接将枢轴原来所处位置的元素覆盖,赋值成需要交换的非枢轴元素,这样序列中会有两个一样的元素,但是枢轴不见了,所以在循环开始之前,需要利用一个辅助变量将枢轴缓存起来,直到其他元素排序完毕(循环结束)以后,再将缓存的枢轴存放到low指向的位置,即可完成排序。
  4. 对于其他元素而言,由于在第一次覆盖的时候,就是覆盖枢轴,以后每一次覆盖都是覆盖传统的交换方法中枢轴应该所在的位置,所以不会发生其它元素被覆盖丢失的情况,在整个过程中,会有两个相同的元素(其中一个就在原来枢轴的位置,是另一个的拷贝)存在,随着high–和low++这个相同的元素是会变化的。

对于小规模的序列

快速排序涉及到了递归操作,在小规模的序列排序的时候,性能还不如简单的直接插入排序,所以在一定规模范围内,采用直接插入排序,当大规模序列的时候,递归带来的性能牺牲可以忽略不计。所以需要设定一个阈值,当大于阈值的时候采用快速排序,当小于阈值的时候需要使用直接插入排序,避免杀鸡用牛刀。

对于递归的考虑

如果待排序的序列划分极为不平衡,根据传统的排序算法,递归的深度可能达到n而不是 logn ,所以如果能减少递归对于空间和时间的节约是非常重要的。在递归处理独立的两部分子序列的时候,我们可以将其改为如下代码:

template <typename T>
void SqList<T>::QSOrt(int low, int high)
{
    int pivot_Position;
    while (low < high)
    {
        pivot_Position = Partition(low,high);
        QSOrt(low, pivot_Position - 1);
        //QSOrt(pivot_Position + 1, high);
        low = pivot_Position + 1;
    }
}

将if改成while循环之后,可以减少一半的递归操作。因为在原来第一次递归以后,变量low就变得毫无用处了,所以将pivot_Position+1赋值给low,再循环后,再来一次Partition(low,high)其效果和QSort(pivot_Position+1,high)是一样的,但采用的是迭代/循环,不是递归,所以可以缩减堆栈深度,从而提高了整体性能。

改进后的快速排序代码

#include <iostream>
using namespace std;

#define MAXSIZE 100
typedef int Status;

#define OK  1
#define ERROR 0
#define TRUE  1
#define FALSE 0

template <typename T>
class SqList
{
public:
    T r[MAXSIZE+1];
    int length;
public:
    SqList(T * var,int len);
    SqList();
    void printList();

    Status swapElement(int i, int j);

    void InsertSort();

    void QuickSort();
    int Partition(int low,int high);
    void QSOrt(int low,int high);
};

template <typename T>
SqList<T>::SqList(T * var,int len)
{
    length = len/sizeof(T);
    memcpy(&(r[1]), var, len);
}

template <typename T>
SqList<T>::SqList()
{
    memset(this, 0, sizeof(SqList));
}

template <typename T>
Status SqList<T>::swapElement(int i,int j)
{
    T tmp =this->r[i];
    this->r[i] = this->r[j];
    this->r[j] = tmp;
    return OK;
}

template <typename T>
void SqList<T>::printList()
{
    for (int i = 1; i <= length; i++)
    {
        cout << this->r[i] << "\t";
    }
    cout << endl;
}


/************************************************************************/
/* 直接插入排序                                                                     */
/************************************************************************/
template <typename T>
void SqList<T>::InsertSort()
{
    int i = 0, j = 0;
    for (i = 2; i <= length; i++)//外层循环表示从无序表中依次将其元素插入到有序表中
                                //默认第一个元素是有序表,所以从第二个元素开始直到第n个元素结束,
                                //这些元素都是无序表中的元素,需要依次把它们插入到前面的有序表中
                                //一次外层循环表示处理好无序表中的一个元素
    {
        if (r[i] < r[i - 1])//第i个元素是无序表中的第一个元素
                            //第i-1个元素是有序表中的最后一个元素,也是有序表中最大的元素
                            //如果无序表中的第一个元素(当前元素)比有序表中的最大元素小
                            //表示需要将当前元素往前插,至于插到什么位置,由内层循环决定
        {
            r[0] = r[i];//利用哨兵将当前元素的值缓存下来
            for (j = i - 1; r[j] > r[0]; j--)//内层循环将比当前元素的值(哨兵的值)大的元素后移挪出空位
            {
                r[j + 1] = r[j];//移动比哨兵大的元素
            }

            r[j + 1] = r[0];//将哨兵插入空位,该空位以前的元素要么为空要么逗比他小,否则不会结束循环
        }
    }//遍历到最后一个元素的时候排序完毕
}


/************************************************************************/
/*快速排序                                                                      */
/************************************************************************/

/*保持和其他排序算法接口一致*/
template <typename T>
void SqList<T>::QuickSort()
{
    QSOrt(1,length);
}

#define MAXCNT 7

/*核心的快排算法*/
template <typename T>
void SqList<T>::QSOrt(int low, int high)
{
    int pivot_Position;//记录枢轴的位置
    if (high - low > MAXCNT)//避免杀鸡用牛刀,整个序列大于8(MAXCNt+1)个用快排
    {
        while (low < high)//减少递归的深度
        {
            pivot_Position = Partition(low, high);//获取枢轴的位置
            QSOrt(low, pivot_Position - 1);//递归处理独立的两部分
                                            //第一次循环的时候处理前半部分
                                            //第二次处理后半部分
            //QSOrt(pivot_Position + 1, high);
            low = pivot_Position + 1;//更新待处理序列的起始位置
        }
    }
    else{//小于8个元素用直接插入排序
        InsertSort();
    }
}

/*交换无序表中子表的元素,使得枢轴移动到该到的位置,并返回其位置
此时前半部分全部比他小
后半部分全部比他大*/
template <typename T>
int SqList<T>::Partition(int low, int high)
{

    T pivot_Var;//记录枢轴的值
    int m = (low + high) / 2;//找到中间元素的下标

    /*三个if语句实现取出左、中、右三个元素的中间值并存放到r[low]*/
    if (r[low] > r[high])//保证r[low]比r[high]小
        swapElement(low, high);

    if (r[m] > r[high])//保证r[m]比r[high]小,至此r[high]肯定是三个元素中最大的
        swapElement(m, high);

    if (r[m] > r[low])//如果r[m]比r[low]大,说明三个元素的中间值是r[m],进行交换操作
        swapElement(low, m);

    pivot_Var = r[low];//得到比较用的枢轴
    r[0] = pivot_Var;//缓存枢轴到哨兵

    while (low < high)
    {

        /*从high开始往前进行划分得到子序列*/
        while (r[high] >= pivot_Var && low < high )
        {
            high--;
        }
        //swapElement(low, high);
        r[low] = r[high];//避免频繁的交换操作


        /*从low开始向后进行划分得到前半部分的子序列*/
        while (r[low] <= pivot_Var && low < high)
        {
            low++;
        }
        //swapElement(low, high);
        r[high] = r[low];//避免交换操作
    }

    r[low] = r[0];//此时的low就是该趟排序中,枢轴应该位于的位置,恢复枢轴
    return low;//返回数轴的位置
}
int main(void)
{
    int myList[9] = {90,10,50,80,30,70,40,60,20};
    SqList<int> list(myList,sizeof(myList));

    cout << "before sort:"<< endl;
    list.printList();

    list.QuickSort();
    cout << "after sort:" << endl;
    list.printList();

    cout<<"Hello!"<<endl;
    system("pause");
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值