Partition算法剖析


partition算法從字面上就非常好理解,就是分割算法嘛!簡單講就是可以把數組按照一定的分成幾個部分,其中最常見的就是快速排序中使用的partition算法,這是一個二分partition算法,將整個數組分解為小於某個數和大於某個數的兩個部分,然后遞歸進行排序算法。
上述只是二分partition算法,我們還會使用三分partition算法,三分partition也有這非常重要的應用。往往我們更多的關注點是快速排序算法等各種算法,以及時間復雜度等這些東西,今天我們專門討論一下partition分割算法的一些應用。

二分 Partition算法

二分partition算法是我們最常使用的,尤其在快速排序中使用最為常見。常見的partition算法有如下兩種實現思路:

思路I

算法思路

  • 使用第一個數組元素作為樞軸點,即為pivot;
  • 使用一個指針去掃描整個數組,凡是小於pivot的全部放到數組左端;
  • 最后講pivot放到數組中間的位置,pivot左邊全部都是小於他的數字,右邊反之,最后返回pivot的位置信息;

代碼

int partition(vector<int> &nums, int begin, int end)
{
    int pivot = nums[begin];
    int pos = begin;
    for(int i = begin+1; i < end; ++i)
    {
        if(nums[i] <= pivot)
            swap(nums[++pos],nums[i]);
    }
    swap(nums[pos], nums[begin]);
    return pos;
}

思路II

算法思路

  • 就如快速排序中最常使用的那樣,使用兩個指針分別從頭部和尾部進行掃描,頭部遇到大於pivot的數和尾部遇到小於pivot的數進行交換;
  • 使用了兩個指針,效率更高一點;

代碼

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

二分partition算法應用

快速排序算法

經典的快速排序算法,直接上代碼:

代碼

void quickSort(vector<int> &nums, int begin, int end)
{
    if(end - begin <= 1)
        return;
    int mid = partition(nums, begin, end);

    quickSort(nums, begin, mid);
    quickSort(nums, mid, end);
}

數組第K大數值查詢

這也是LeetCode中的一道例題,非常適合使用partition算法進行解決,問題鏈接215. Kth Largest Element in an Array

解題思路

  • 首先可以通過排序進行求解,簡單暴力;
  • 不斷使用partition算法進行迭代查找;

代碼

class Solution
{
    public:
        int findKthLargest(vector<int> &nums, int k)
        {
            int len = nums.size();
            int res = 0;
            int left = 0;
            int right = len;
            while(left < right)
            {
                int pos = partition(nums, left, right);
                if(pos == len-k)
                {
                    res = nums[pos];
                    break;
                }
                else if(pos < len-k)
                    left = pos+1;
                else
                    right = pos;
            }
            return res;
        }
        int partition(vector<int> &nums, int begin, int end)
        {
            int pivot = nums[begin];
            while(begin < end)
            {
                while(begin < end && nums[--end] >= pivot);
                nums[begin] = nums[end];
                while(begin < end && nums[++begin] <= pivot);
                nums[end] = nums[begin];
            }
            nums[begin] = pivot;
            return begin;
        }
};

三分paitition算法

三分partition算法,顧名思義,也就是將數組按照規則分為三個部分,比如非常經典的國旗問題Dutch national flag problem,就是要給定的紅、白、藍三色隨機顏色小球按照紅、白、藍的順序進行排序,利用partition算法,使用一個指針進行掃描,紅色的小球就用swap()放到左邊,白色的保持位置不變,藍色的同樣使用swap()放到右邊,最后就得到要求的序列了。

Dutch National Flag Problem

LeetCode中有恰好有這么一個題:75. Sort Colors

解題思路

  • 就使用三分partition算法進行求解就可以了!

代碼

class Solution
{
    public:
        void sortColors(vector<int> &nums)
        {
            int len = nums.size();
            int left = 0;
            int right = len - 1;
            for(int i = 0; i < len; ++i)
            {
                if(i > right)
                    break;
                if(nums[i] == 1)
                    continue;
                else if(nums[i] == 0)
                {
                    swap(nums[i], nums[left]);
                    left++;
                }
                else
                {
                    swap(nums[i], nums[right]);
                    right--;
                    i--;
                }
            }
        }
};

進階應用

LeetCode 324. Wiggle Sort II

LeetCode中的第324題中也同樣可以使用三分partition算法,該題的discuss中,StefanPochmann大神提出一種O(n)+O(1)復雜度的高效算法,原鏈接為:
324. Wiggle Sort II
Discuss

解題思路

  • 使用partition算法獲取數組的中位數,這個思路同找第k大的數,這里作者用了c++中的nth_element()函數;
  • 使用宏定義的方式#define A(i) nums[(1+2*(i)) % (n|1)]A()的前半部分對應nums中下標為奇數的元素,后半部分為偶數,即奇數 + 偶數
  • 使用三分partition算法對A()進行排序,使其前半部分大於后半部分,即在nums中奇數部分 > 偶數部分
  • 最終達到的效果為 0 < 1 > 2 < 3 > 4 < 5 ...
  • 注意這里需要的是奇數>偶數,所以進行partition的時候大於pivot樞軸值的要放到前面;

#define A(i) nums[(1+2*(i)) % (n|1)]的作用如下所示:
假設有0, 1, 2, 3, 4, 5, 6, 7, 8, 9共10個數據,則使用A()進行映射之后的結果為:
A(0) -> nums[1].
A(1) -> nums[3].
A(2) -> nums[5].
A(3) -> nums[7].
A(4) -> nums[9].
A(5) -> nums[0].
A(6) -> nums[2].
A(7) -> nums[4].
A(8) -> nums[6].
A(9) -> nums[8].

代碼

class Solution
{
    public:
        void wiggleSort(vector<int>& nums) 
        {
            int n = nums.size();
    
            // Find a median.
            auto midptr = nums.begin() + n / 2;
            nth_element(nums.begin(), midptr, nums.end());
            int mid = *midptr;
    
            // Index-rewiring.
            #define A(i) nums[(1+2*(i)) % (n|1)]

            // 3-way-partition-to-wiggly in O(n) time with O(1) space.
            int i = 0, j = 0, k = n - 1;
            while (j <= k) 
            {
                if (A(j) > mid)
                    swap(A(i++), A(j++));
                else if (A(j) < mid)
                    swap(A(j), A(k--));
                else
                    j++;
            }
        }
};

github Githubhttps://github.com/haoyuanliu
個人博客 個人博客http://haoyuanliu.github.io/

個人站點,歡迎訪問,歡迎評論!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM