快速排序中的套路

引言

算法是码农的套路。而提炼算法中的套路,然后学以致用,才能进阶。

思想

快速排序算法的基本思想是分治策略(Divide-and-Conquer Method)。步骤如下:
1. 选取一个基准元素(pivot)
2. 比pivot小的放到pivot左边,比pivot大的放到pivot右边
3. 对pivot左边的序列和右边的序列分别递归的执行步骤1和步骤2,直到各区间只有一个数。

伪代码如下:

QUICKSORT(A, begin, end)
    if begin < end    
        q = PARTITION(A, begin, end)
        QUICKSORT(A, begin, q-1)
        QUICKSORT(A, q+1, end)

//将数组分为两个子数组,左边的小于主元,右边的大于主元,返回临界值下标
PARTITION(A, begin, end)
    pivot = A[end]    //以最后一个数为主元(pivot element)
    left_end = begin-1 //left_end指向左子数组的尾部
    for i = begin to end-1//遍历整个数组
        if A[i] < pivot//找到了左子数组中的元素
            left_end = left_end+1 //增加左子数组的大小
            exchange A[left_end] with A[i] //将A[i]换入左子数组,换出的left_end肯定比主元大
    exchange A[left_end+1] with A[end]   //将主元从数组末尾移动至子数组之间
    return left_end + 1

关于“换出的left_end肯定比主元大”:遍历到大于主元的数时,仅i增加,left_end不变;一旦遍历到小于主元的数,left_end = left_end+1,left_end指向下一个大于主元的数,将该数与最新遍历到的小于主元的数交换
例如:i从begin开始遍历,假如第一个数begin大于主元,此时++i,不交换;假如第二个数begin+1小于主元,则left_end = left_end+1,此时left_end指向begin,交换后将小于主元的A[i]换入左子数组,大于主元的left_end换出。

代码

int mypartition(vector<int>&arr, int begin, int end)
{
    int pivot = arr[end];//选最后一个元素作为主元
    int location = begin-1;//location指向比pivot小的元素段的尾部
    for(int i = begin; i < end; i++)//比主元小的元素依次放在前半部分
       if(arr[i] < pivot)
           swap(arr[i], arr[++location]);
    swap(arr[end], arr[location+1]);
    return location+1;
}

快速排序主函数:

void quicksort(vector<int>&arr, int begin, int end)
{
    if(begin < end)
    {
        int middle = mypartition(arr, begin, end);
        quicksort(arr, begin, middle-1);
        quicksort(arr, middle+1, end);
    }
}

其他

1.快速排序另一种写法更便于理解:

int mypartition(vector<int>&arr, int low, int high)
{
    int pivot = arr[low];//选第一个元素作为枢纽元
    while(low < high)
    {
        while(low < high && arr[high] >= pivot)high--;
        arr[low] = arr[high];//从后面开始找到第一个小于pivot的元素,放到low位置
        while(low < high && arr[low] <= pivot)low++;
        arr[high] = arr[low];//从前面开始找到第一个大于pivot的元素,放到high位置
    }
    arr[low] = pivot;//最后枢纽元放到low的位置
    return low;
}

2.与上面思想类似的套路:

int mypartition(vector<int>&a, int left, int right)
{
    int i,j,t,temp; 
    temp=a[left]; //temp中存的就是基准数 
    i=left; 
    j=right; 
    while(i!=j) 
    { 
        //顺序很重要,要先从右边开始找 
        while(a[j]>=temp && i<j) 
            j--; 
        //再找右边的 
        while(a[i]<=temp && i<j) 
            i++; 
        //交换两个数在数组中的位置 
        if(i<j) 
        { 
            t=a[i]; 
            a[j]=a[j]; 
            a[j]=t; 
        } 
    } 
    //最终将基准数归位 
    a[left]=a[i]; 
    a[i]=temp; 

    return i;
}

3.关于基准数选取:最好的方法是用一个随机函数产生一个取位于 left 和 right 之间的随机数 k(left≤k≤right),这样可以使划分基准的选择是随机的,从而可以期望划分是较对称的。

4.Partition函数除了用在快速排序,还可以用O(n)的平均时间复杂度从无序数组中寻找第k大的值:

int GetArrayMaxK(int array[], int nStart, int nEnd, int k)  
{  
    if (k <= 0)  
    {  
        throw;  
    }  
    int nPos = -1;  
    while (true)  
    {  
        nPos = Partition(array, nStart, nEnd);  
        if ((nPos+1) == k)  //左边的数均小于array[nPos]
        {  
            return array[nPos];  
        }else if ((nPos+1) > k)  
        {  
            nEnd = nPos - 1;  
        }else  
        {  
            nStart = nPos + 1;  
        }  
    }  
}

参考

快速排序partition过程常见的两种写法+快速排序非递归实现
坐在马桶上看算法:快速排序
算法导论-排序(二)快速排序、随机化快速排序
排序算法(三)–关于快速排序Partition的思考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值