八大排序之 快速排序

快速排序

思想:选择一个基准,下标h指向最后一个元素,下标l指向第一个元素。下标h从后向前遍历,比基准小的数字放在前面,下标l从前向后遍历,比基准大的放后面。重复以上操作,直到 l、h相遇,相遇点即为基准要放的位置。直到只剩一个数字的时候,停止划分。
时间复杂度:平均O(nlogn) 最好 O(nlogn) 最坏O(n^2)
空间复杂度:O(logn)
稳定性:不稳定
优点:越无序越快
缺点:有序的时候效率低
适用于:乱序的序列

不论用递归还是非递归,都需要这个一次划分过程

int Pariton(int *arr,int low,int high)//一次划分
{
    if(arr == NULL || low < 0 || high < 0 || low > high) return-1;
      优化2int mid = (high - low)/2 + low;
         FindSecondMaxNumber(arr,low,mid,high);
     int i = low;
    int j = high;
    int tmp = arr[low];//选择基准点
    while(i < j)//开始循环
    {
         while(i<j && tmp <= arr[j])   j--;//是否往前走
         if(i < j)    arr[i]=arr[j];
         while(i < j && tmp >= arr[i])   i++;//是否往后走
         if(i < j)   arr[j] = arr[i];
    }
    arr[i] = tmp;//找到基准点要放的位置
    return i;
}

用递归进行排序:

void QSort(int *arr,int low,int high)
{
    int par = Pariton(arr,low,high);//先一次划分,par是一次遍历以后基准要放的位置。此时par左边的数字都小于par,par右边的数字都大于par。
    if(par > low+1)//不断划分左边,par > low+1是因为可以保证par左边最少有两个数字,才再次进行划分
    {
          QSort(arr,low,par);
    }
    if(par < high - 1)//不断划分右边,前提是par右边至少有两个数字
    {
          QSort(arr,par+1,high);
    }
}
void QuickSort(int *arr,int len)
{
    QSort(arr,0,len-1);
}

非递归的方法,用栈实现:

void QuickSortstack(int*arr,int len)
{
    int i = 0;;
    int j = len-1;
    stack<int> st;
    int par = Pariton(arr,i,j);//先一次划分,par是一次遍历以后基准要放的位置
    if(par > i+1)//左边继续划分
    {
         st.push(i);//入栈时先如左下标
         st.push(par);//再入这次划分中的右下标
    }
    if(par < j - 1)
    {
         st.push(par+1);//入栈时先入这次划分的左下标
         st.push(j);//再入右下标
    }
    while(!st.empty())
    {
         j = st.top(); st.pop();//出栈时先出右下标
         i = st.top(); st.pop();//再出左下标
         int par = Pariton(arr,i,j);//在while循环里面继续划分
         if(par > i+1)
         {
             st.push(i);
             st.push(par);
         }
         if(par < j - 1)
         {
             st.push(par+1);
             st.push(j);
         }
    }
}

优化1:三分取中法
引入三分取中的原因是:选取基准时,用第一个元素作为基准这是很不好的方法,最坏情况(有序)时间复杂度还是O(n^2)。所以最佳的划分思路是将待排序序列分成等长的子序列,但是如果一定要刚好等长的话这会很难计算。
因此,一般取左端、右端和中间三个位置上元素中的中间值作为基准,假如下标用low、mid、high来表示,那么将这个中间值存储在low下标处,依然将low处作为基准。
先提供交换函数,目的是将数组中的左、中、右三个位置上的值进行按大小排列,大小顺序要求是 arr[mid] < arr[low] < arr[high],low处存储的是“中间值”

void Swap(int *arr,int firstindex,int secondindex)
{
    int tmp = arr[firstindex];
    arr[firstindex] = arr[secondindex];
    arr[secondindex]=tmp;
}

接下来是把中间值放在合适的位置上,“中间值”其实也就是左、中、右三个断点的第二大的值

void FindSecondMaxNumber(int *arr,int low,int mid,int high)
{   //目标是arr[mid] < arr[low] < arr[high]
    if(arr[mid] > arr[high])//目标是mid的值比high小
         Swap(arr,mid,high);
    if(arr[mid] > arr[low])//目标是mid的值比low小
         Swap(arr,mid,low);
    if(arr[low] > arr[high])//目标是low的值比high小
         Swap(arr,low,high);
}

以上就是第一种“三分取中”的优化

优化2:当数据量较小时,比如10个元素,用直接插入排序即可。

if(high - low +1 < 10)
{
    insertSort(arr,low,high);
    return ;
}

优化3:聚集相同的关键字/基准点,不进行下一趟排序
如果在待排序序列中,有和基准点相同的值,那么我们可以将这些相同的值放在一起,每次递归时将相同基准值的最左和最右传进去,这样这些相同的数字就不用进行排序

void gather(int arr[],int low,int high,int par,int *left,int* right)
{
    if(low < high)//首先要满足这个条件
    {
         int count = par - 1;//1.在基准左侧寻找与基准相同的值
         for(int i = count ; i >=low;--i)
         {
             if(i != count && arr[i] == arr[par])
                    //i不是count并且当前值和基准点相同
             {
                 Swap(arr,i,count);
                 count--;//向左减
             }
         }
         *left = count;//记录相同关键字的左下标
         count = par + 1;//2.在基准右侧寻找与基准相同的值
         for(int i= count ; i<=high;++i)
         {
             if(i != count && arr[i] == arr[par])
             {
                 Swap(arr,i,count);
                 count++;//向右加
             }
         }
         *right = count;//记录相同关键字的右下标
    }
}

//下面这个函数仅仅是如何在递归中调用聚集关键字的函数
void Qsort(int *arr,int low,int high)
{
     int par = Pariton(arr,low,high);
     int left = low;//left将要记录相同基准的左下标
     int right = high;//right将要记录相同基准的右下标
     gather(arr,low,high,par,left,right);//优化3

    if(par > low+1)
    {
          QSort(arr,low,left);//传入右下标时传入记录的相同基准的左边就可以,相同基准不用再进行排序
    }
    if(par < high - 1)
    {
          QSort(arr,right,high);//传入左下标时传入记录的相同基准的右边就可以,相同基准不用再进行排序
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值