- 博文鏈接:http://haoyuanliu.github.io/2016/12/18/Partition算法剖析/
- 對,我是來騙訪問量的!O(∩_∩)O~~
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: https://github.com/haoyuanliu
個人博客: http://haoyuanliu.github.io/
個人站點,歡迎訪問,歡迎評論!