diff --git a/sorts/quick_sort_3_partition.py b/sorts/quick_sort_3_partition.py index 279b9a68f5a6..13ddeda0556d 100644 --- a/sorts/quick_sort_3_partition.py +++ b/sorts/quick_sort_3_partition.py @@ -1,3 +1,6 @@ +import random + + def quick_sort_3partition(sorting: list, left: int, right: int) -> None: """ " Python implementation of quick sort algorithm with 3-way partition. @@ -88,6 +91,139 @@ def lomuto_partition(sorting: list, left: int, right: int) -> int: return store_index +def hoare_partition_by_value( + array: list, pivot_value: int, start: int = 0, end: int | None = None +) -> int: + """ + Returns the starting index of the right subarray, which contains the + elements greater than or equal to `pivot_value` + + >>> list_unsorted = [7, 3, 5, 4, 1, 8, 6] + >>> array = list_unsorted.copy() + >>> hoare_partition_by_value(array, 5) + 3 + >>> array + [1, 3, 4, 5, 7, 8, 6] + + Edge cases: + >>> hoare_partition_by_value(list_unsorted.copy(), 0) + 0 + >>> hoare_partition_by_value(list_unsorted.copy(), 1) + 0 + >>> hoare_partition_by_value(list_unsorted.copy(), 2) + 1 + >>> hoare_partition_by_value(list_unsorted.copy(), 8) + 6 + >>> hoare_partition_by_value(list_unsorted.copy(), 9) + 7 + + """ + if end is None: + end = len(array) - 1 + + left = start + right = end + + while True: + """ + In an intermediate iteration, state could look like this: + + lllluuuuuuuuuurrrrr + ^ ^ + | | + left right + + Where the middle values are [u]nknown, since they are not yet traversed. + `left-1` points to the end of the left subarray. + `right+1` points to the start of the right subarray. + """ + + while array[left] < pivot_value: + left += 1 + if left > end: + # Right subarray is empty. + # Signal it by returning an index out of bounds. + return end + 1 + while array[right] >= pivot_value: + right -= 1 + if right < start: + # Left subarray is empty + return start + + if left > right: + break + + # Invariants: + assert all(i < pivot_value for i in array[start:left]) + assert all(i >= pivot_value for i in array[right + 1 : end]) + """ + llllllruuuuulrrrrrr + ^ ^ + | | + left right + """ + + # Swap + array[left], array[right] = array[right], array[left] + + left += 1 + right -= 1 + + return right + 1 + + +def hoare_partition_by_pivot( + array: list, pivot_index: int, start=0, end: int | None = None +) -> int: + """ + Returns the new pivot index after partitioning + + >>> array = [7, 3, 5, 4, 1, 8, 6] + >>> array[3] + 4 + >>> hoare_partition_by_pivot(array, 3) + 2 + >>> array + [1, 3, 4, 6, 7, 8, 5] + """ + if end is None: + end = len(array) - 1 + + def swap(i1, i2): + array[i1], array[i2] = array[i2], array[i1] + + pivot_value = array[pivot_index] + swap(pivot_index, end) + greater_or_equal = hoare_partition_by_value( + array, pivot_value, start=start, end=end - 1 + ) + swap(end, greater_or_equal) + return greater_or_equal + + +def quicksort_hoare(array: list, start: int = 0, end: int | None = None): + """ + Quicksort using the Hoare partition scheme: + - https://en.wikipedia.org/wiki/Quicksort#Hoare_partition_scheme + - The Art of Computer Programming, Volume 3: Sorting and Searching + + >>> array = [2, 2, 8, 0, 3, 7, 2, 1, 8, 8] + >>> quicksort_hoare(array) + >>> array + [0, 1, 2, 2, 2, 3, 7, 8, 8, 8] + """ + if end is None: + end = len(array) - 1 + + if end + 1 - start <= 1: + return + + pivot_index = random.randrange(start, end) + pivot_index_final = hoare_partition_by_pivot(array, pivot_index, start, end) + quicksort_hoare(array, start, pivot_index_final - 1) + quicksort_hoare(array, pivot_index_final + 1, end) + + def three_way_radix_quicksort(sorting: list) -> list: """ Three-way radix quicksort: