Skip to content

Commit 532bbc2

Browse files
committed
Add Hoare partition algorithm
This is another variant of in-place quicksort apart from 'Lomuto' and the 'Dutch National Flag algorithm'
1 parent fff34ed commit 532bbc2

File tree

1 file changed

+136
-0
lines changed

1 file changed

+136
-0
lines changed

sorts/quick_sort_3_partition.py

+136
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import random
2+
3+
14
def quick_sort_3partition(sorting: list, left: int, right: int) -> None:
25
""" "
36
Python implementation of quick sort algorithm with 3-way partition.
@@ -88,6 +91,139 @@ def lomuto_partition(sorting: list, left: int, right: int) -> int:
8891
return store_index
8992

9093

94+
def hoare_partition_by_value(
95+
array: list, pivot_value: int, start: int = 0, end: int | None = None
96+
) -> int:
97+
"""
98+
Returns the starting index of the right subarray, which contains the
99+
elements greater than or equal to `pivot_value`
100+
101+
>>> list_unsorted = [7, 3, 5, 4, 1, 8, 6]
102+
>>> array = list_unsorted.copy()
103+
>>> hoare_partition_by_value(array, 5)
104+
3
105+
>>> array
106+
[1, 3, 4, 5, 7, 8, 6]
107+
108+
Edge cases:
109+
>>> hoare_partition_by_value(list_unsorted.copy(), 0)
110+
0
111+
>>> hoare_partition_by_value(list_unsorted.copy(), 1)
112+
0
113+
>>> hoare_partition_by_value(list_unsorted.copy(), 2)
114+
1
115+
>>> hoare_partition_by_value(list_unsorted.copy(), 8)
116+
6
117+
>>> hoare_partition_by_value(list_unsorted.copy(), 9)
118+
7
119+
120+
"""
121+
if end is None:
122+
end = len(array) - 1
123+
124+
left = start
125+
right = end
126+
127+
while True:
128+
"""
129+
In an intermediate iteration, state could look like this:
130+
131+
lllluuuuuuuuuurrrrr
132+
^ ^
133+
| |
134+
left right
135+
136+
Where the middle values are [u]nknown, since they are not yet traversed.
137+
`left-1` points to the end of the left subarray.
138+
`right+1` points to the start of the right subarray.
139+
"""
140+
141+
while array[left] < pivot_value:
142+
left += 1
143+
if left > end:
144+
# Right subarray is empty.
145+
# Signal it by returning an index out of bounds.
146+
return end + 1
147+
while array[right] >= pivot_value:
148+
right -= 1
149+
if right < start:
150+
# Left subarray is empty
151+
return start
152+
153+
if left > right:
154+
break
155+
156+
# Invariants:
157+
assert all(i < pivot_value for i in array[start:left])
158+
assert all(i >= pivot_value for i in array[right + 1 : end])
159+
"""
160+
llllllruuuuulrrrrrr
161+
^ ^
162+
| |
163+
left right
164+
"""
165+
166+
# Swap
167+
array[left], array[right] = array[right], array[left]
168+
169+
left += 1
170+
right -= 1
171+
172+
return right + 1
173+
174+
175+
def hoare_partition_by_pivot(
176+
array: list, pivot_index: int, start=0, end: int | None = None
177+
) -> int:
178+
"""
179+
Returns the new pivot index after partitioning
180+
181+
>>> array = [7, 3, 5, 4, 1, 8, 6]
182+
>>> array[3]
183+
4
184+
>>> hoare_partition_by_pivot(array, 3)
185+
2
186+
>>> array
187+
[1, 3, 4, 6, 7, 8, 5]
188+
"""
189+
if end is None:
190+
end = len(array) - 1
191+
192+
def swap(i1, i2):
193+
array[i1], array[i2] = array[i2], array[i1]
194+
195+
pivot_value = array[pivot_index]
196+
swap(pivot_index, end)
197+
greater_or_equal = hoare_partition_by_value(
198+
array, pivot_value, start=start, end=end - 1
199+
)
200+
swap(end, greater_or_equal)
201+
return greater_or_equal
202+
203+
204+
def quicksort_hoare(array: list, start: int = 0, end: int | None = None):
205+
"""
206+
Quicksort using the Hoare partition scheme:
207+
- https://en.wikipedia.org/wiki/Quicksort#Hoare_partition_scheme
208+
- The Art of Computer Programming, Volume 3: Sorting and Searching
209+
210+
>>> array = [2, 2, 8, 0, 3, 7, 2, 1, 8, 8]
211+
>>> quicksort_hoare(array)
212+
>>> array
213+
[0, 1, 2, 2, 2, 3, 7, 8, 8, 8]
214+
"""
215+
if end is None:
216+
end = len(array) - 1
217+
218+
if end + 1 - start <= 1:
219+
return
220+
221+
pivot_index = random.randrange(start, end)
222+
pivot_index_final = hoare_partition_by_pivot(array, pivot_index, start, end)
223+
quicksort_hoare(array, start, pivot_index_final - 1)
224+
quicksort_hoare(array, pivot_index_final + 1, end)
225+
226+
91227
def three_way_radix_quicksort(sorting: list) -> list:
92228
"""
93229
Three-way radix quicksort:

0 commit comments

Comments
 (0)