Partition Algorithm

Introduction

Partition algorithm is one of my favorite $O(N)$ algorithms as it is efficient and extremely useful in practice. It is also a fundamental building block for sorting algorithms such as Quicksort and partial sort, and linear-time worst-case selection algorithm.

In this blog post, I would like to quickly discuss the partition algorithm.

Partition Algorithm

Custom Two-Pointer Approach

partition.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>

template <class ForwardIt, class UnaryPredicate>
constexpr ForwardIt custom_partition(ForwardIt first, ForwardIt last,
UnaryPredicate p)
{
// Two-pointer approach
if (first == last)
{
return first;
}
--last;
while (first != last)
{
while (first != last && p(*first))
{
++first;
}
while (first != last && !p(*last))
{
--last;
}
// Swap values
std::swap(*first, *last);
}
return first;
}

int main()
{
std::vector<int> v{0, 55, 66, 77, 11, 22, 33, 44, 88, 99};
std::cout << "Original vector:" << std::endl;
std::copy(std::begin(v), std::end(v),
std::ostream_iterator<int>(std::cout, " "));
std::cout << std::endl;
auto it =
custom_partition(v.begin(), v.end(), [](int i) { return i < 60; });

std::cout << "Partitioned vector:" << std::endl;
std::copy(std::begin(v), it, std::ostream_iterator<int>(std::cout, " "));
std::cout << " * "
" ";
std::copy(it, std::end(v), std::ostream_iterator<int>(std::cout, " "));
std::cout << std::endl;
}
1
2
3
4
5
6
$ g++ partition.cpp -o partition --std=c++14
$ ./partition
Original vector:
0 55 66 77 11 22 33 44 88 99
Partitioned vector:
0 55 44 33 11 22 * 77 66 88 99

C++ Standard Library Function

C++ standard library has provided the standard partition function std::partition for us. So instead of using the custom_partition function we implemented in the above example, we could just use std::partition.

The possible implementation suggested by CPP Reference is as follows.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <class ForwardIt, class UnaryPredicate>
constexpr ForwardIt custom_partition(ForwardIt first, ForwardIt last,
UnaryPredicate p)
{
first = std::find_if_not(first, last, p);
if (first == last) return first;

for (ForwardIt i = std::next(first); i != last; ++i) {
if (p(*i)) {
std::iter_swap(i, first);
++first;
}
}
return first;
}

The major difference between this implementation and our implementation was that the two pointers used in this implementation are both sliding from left to right whereas the two pointers used in our implementation are sliding from different directions. Therefore, this implementation will have at most $N$ swaps whereas our implementation will have at most $\frac{N}{2}$ swaps.

This implementation is somewhat more general than the one we implemented above in my opinion, because it follows the half open range convention in C++ STL and it did not use -- which might not have been implemented by the iterator of each STL container.

Author

Lei Mao

Posted on

09-07-2023

Updated on

09-07-2023

Licensed under


Comments