Partition算法记录

来源:互联网 发布:淘宝电脑客户端 编辑:程序博客网 时间:2024/06/06 01:58

转自:被忽视的 partition 算法:http://blog.jobbole.com/105219/
版权归原作者所有 如有侵权请联系作者予以删除。

算法介绍

首先以无序数组中的元素为枢轴点 pivot,经过一次遍历,将数组中其他元素分为以 pivot 为分界线的两部分,使得左边部分的数小于等于pivot,右边部分的数大于等于pivot(左部分或者右部分都可能为空),最后返回pivot在新的数组中的位置。

算法原理

指针单向遍历

以数列的第一个元素为轴点,单向遍历
arr[begin, end)

// Do partition in arr[begin, end), with the first element as the pivot.int partition(vector<int>&arr, int begin, int end){    int pivot = arr[begin];    // Last position where puts the no_larger element.    int pos = begin;    for(int i=begin+1; i!=end; i++){        if(arr[i] <= pivot){            pos++;            if(i!=pos){                swap(arr[pos], arr[i]);            }        }    }    swap(arr[begin], arr[pos]);    return pos;}

如果原始数组为[5,9,2,1,4,7,5,8,3,6],那么整个处理的过程如下图所示:

partition的单向实现

指针双向遍历

Two Pointers 的思想,保持头尾两个指针向中间扫描,每次在头部找到大于pivot的值,同时在尾部找到小于pivot的值,然后将它们做一个交换,就可以一次把这两个数字放到最终的位置。一种比较明智的写法如下:

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;}

同样以原始数组[5,9,2,1,4,7,5,8,3,6]为例,处理流程为:

partition的双向实现

直观上来看,赋值操作的次数不多,比前面单向扫描的swap次数都少,效率应该会更高。
但是直觉上总是感觉如果将双向遍历等同于两次单向遍历,效率应该差不多啊。。。

应用1:快速排序

首先用 partition 将数组分为两部分,然后分别对左右两部分递归进行快速排序,过程如下:

void quick_sort(vector<int> &arr, int begin, int end){    if(begin >= end - 1){        return;    }    int pos = partition(arr, begin, end);    quick_sort(arr, begin, pos);    quick_sort(arr, pos+1, end);}

应用2:从无序数组中寻找从小到大第k个值

首先用 partition 将数组分为两部分,得到分界点下标 pos,然后分三种情况:

pos == k-1,则找到第 K 大的值,arr[pos];
pos > k-1,则第 K 大的值在左边部分的数组。
pos < k-1,则第 K 大的值在右边部分的数组。

int find_kth_number(vector<int> &arr, int k){    int begin = 0, end = arr.size();    assert(k>0 && k<=end);    int target_num = 0;    while (begin < end){        int pos = partition(arr, begin, end);        if(pos == k-1){            target_num = arr[pos];            break;        }        else if(pos > k-1){            end = pos;        }        else{            begin = pos + 1;        }    }    return target_num;}

应用3:三色旗问题

问题描述:
给定红、白、蓝三种颜色的小球若干个,将其排成一列,使相同颜色的小球相邻,三种颜色先后顺序为红,白,蓝。

最直接的想法是对红,蓝,白三种颜色的球分别计数,然后根据计数结果来重新放球。
不过如果我们将问题进一步抽象,也就是说将一个数组按照某个target值分为三部分,使得左边部分的值小于 target,中间部分等于 target,右边部分大于 target,这样就不能再用简单的计数来确定排序后的结果。

  • next_less_pos 指向左边部分的边界,等待存放下一个小于target的值
  • next_bigger_pos指向右边部分的边界,等待存放下一个大于target的值
  • next_scan_pos 为移动遍历指针

指针示意图

思想:不断减少未知区域的元素 当scan指针和bigger指针重合,表示未知区域减小为0, 结束遍历。

// Assume target is in the arr.void three_way_partition(vector<int> &arr, int target){    int next_less_pos = 0, next_bigger_pos = arr.size()-1;    int next_scan_pos = 0;    while (next_scan_pos <= next_bigger_pos){        if(arr[next_scan_pos] < target){            swap(arr[next_scan_pos++], arr[next_less_pos++]);        }        else if(arr[next_scan_pos] > target){            swap(arr[next_scan_pos], arr[next_bigger_pos--]);        }        else{            next_scan_pos++;        }    }}
原创粉丝点击