Partition 算法详解 + 应用 + 代码实现(一)

听说三四年前,MSRA的面试题就是手撕鼎鼎大名的快排算法(现在估计已经男上加男了),其实如果了解了Partition的核心思想,写快排简直是分分钟啊!长话短说,今天就来讲一讲Partition算法的思想。

Partition 除了用在快速排序中,还广泛用于在无序数组中寻找第K大的值的算法中,Partition算法可以有效的将O(NlogN)降至O(N),算法思想是通过一次扫描就解决任务。

Partition的主要过程就是首先确定一个值privot,这个值就是划分数组的依据,大于等于privot的都位于privot右边,小于等于privot的都位于privot左边,最后再返回privot最终所在的位置即可。根据这个算法思路,一个实现Partition算法的简单版本如下,该算法从左到右扫描对数组进行划分,定义一个指针位置pos,在扫描的过程中,pos指向不满足条件待交换的元素的位置,一旦发现需要交换,不断交换元素即可。其中函数变量privot_posisiton表示的是对privot元素最初在数组arr中的位置。

//正向游走
void partition_pos(int arr[], int beginn, int endd, int privot_posisiton){

    if (privot_posisiton >= endd && privot_posisiton < beginn){
        return ;
    }

    int privot =  arr[privot_posisiton];

    int pos = beginn - 1;

    for (int i = beginn; i < endd; i++){
        if (arr[i] <= privot){
            pos++;
            if (pos < i){
                swap(arr[i], arr[pos]);
            }
        }
/*
        cout<<"i = "<<i<<" ; pos = "<<pos<<" ; ";
        for (int j = beginn; j < endd; j++){
            cout<<arr[j]<<" ";
        }
        cout<<endl;
*/
    }
    //swap(arr[pos], arr[privot_posisiton]);
}

除了从左到右扫描对数组进行划分,也可以从右到左扫描进行划分,本质上代码没有不同,代码如下:


//逆向游走
void partition_neg(int arr[], int beginn, int endd, int privot_posisiton){

    if (privot_posisiton >= endd && privot_posisiton < beginn){
        return ;
    }

    int privot =  arr[privot_posisiton];

    int pos = endd;

    for (int i = endd - 1; i >= beginn; i--){
        if (arr[i] >= privot){
            pos--;
            if (pos > i){
                swap(arr[i], arr[pos]);
            }
        }
/*
        cout<<"i = "<<i<<" ; pos = "<<pos<<" ; ";
        for (int j = beginn; j < endd; j++){
            cout<<arr[j]<<" ";
        }
        cout<<endl;
*/
    }
    //swap(arr[pos], arr[privot_posisiton]);
}

观察这两个算法,我们可以发现,待交换的元素总是位于pos位置,再交换到此时扫描的位置,但这是此时扫描的位置不一定位于privot之后,因此会产生对同一个元素的多次交换,算法效率不高。结合从左扫描和从右扫描的思想,我们可以在一次循环中同时从左至右和从右至左扫描,然后交换两元素的位置,直到两头扫描的指针汇合扫描结束,代码如下:



//双向游走
void partition_bot(int arr[], int beginn, int endd, int privot_posisiton){

    if (privot_posisiton >= endd && privot_posisiton < beginn){
        return ;
    }

    int privot =  arr[privot_posisiton];

    endd = endd - 1;


    while (beginn < endd){
        while (beginn < endd && arr[beginn] < privot){
            beginn++;
        }
        while (beginn < endd && arr[endd] > privot){
            endd--;
        }
        if (beginn >= endd){
            break;
        }
        else{
            swap(arr[beginn], arr[endd]);
            beginn++;
            endd--;
        }
/*
        cout<<"begin = "<<beginn<<" ; end = "<<endd<<" ; ";
        for (int j = 0; j < 10; j++){
            cout<<arr[j]<<" ";
        }
        cout<<endl;
*/
    }
}

除此之外,我看到一篇非常不错的博客,博客里也写到了双向扫描算法,比我写的简洁,粘贴如下:

int partition(vector<int>&arr, int begin, int end)
{
    int pivot = arr[begin];
    while(begin < end)
    {
        while(begin < end && arr[--end] >= pivot);
        arr[begin] = arr[end];
        while(begin < end && arr[++begin] <= pivot);
        arr[end] = arr[begin];
    }
    arr[begin] = pivot;
    return begin;
}

值得注意的是,这个算法也用到了swap的思想,但是非常隐晦,程序的意思是,我先把第一个数保存下来,第二个待交换的数赋值给第一个数,第三个待交换的数赋值给第二个数,以此类推,最后把保存的第一个数赋值给最后一个数,完成了交换,算法效率明显更高。参考博客(http://selfboot.cn/2016/09/01/lost_partition/)图例如下:

这里有Partition单向游走和双向游走算法复杂度比较的博客: https://cs.stackexchange.com/questions/11458/quicksort-partitioning-hoare-vs-lomuto/11550

 

 

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值