Skip to content

Commit e6c9328

Browse files
implemented randomized and deterministic Median searches (QuickSelect and MdM) for Quciksort
1 parent d00be15 commit e6c9328

File tree

5 files changed

+181
-1
lines changed

5 files changed

+181
-1
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package algorithms.search
2+
3+
/// Deterministic Median of Medians Search returns the median of the Array A
4+
/// Used for a deterministic O(n) pivot of QuickSort
5+
/// This runs in O(n) because T(n) <= T(n/5) + T(7n / 10) + O(n)
6+
7+
def MdMSelect(A: Array[Int], k: Int): Int = {
8+
if (A.length <= 25) return bruteForceMedian(A, k) // Use sorting for small arrays
9+
10+
// Divide A into groups of 5 and find medians
11+
val groups = A.grouped(5).toArray
12+
val Medians = groups.map(group => bruteForceMedian(group, group.length / 2))
13+
14+
// Recursively find median of medians
15+
val mdm = MdMSelect(Medians, Medians.length / 2)
16+
17+
// Partition around mdm
18+
val (less, equal, greater) = A.foldLeft((List[Int](), List[Int](), List[Int]())) {
19+
case ((less, equal, greater), elem) =>
20+
if (elem < mdm) (elem :: less, equal, greater)
21+
else if (elem > mdm) (less, equal, elem :: greater)
22+
else (less, elem :: equal, greater)
23+
}
24+
25+
if (less.length >= k) {
26+
MdMSelect(less.toArray, k)
27+
} else if (less.length + equal.length >= k) {
28+
mdm
29+
} else {
30+
MdMSelect(greater.toArray, k - less.length - equal.length)
31+
}
32+
}
33+
def bruteForceMedian(A: Array[Int], k: Int): Int = {
34+
A.sorted.apply(k)
35+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package algorithms.search
2+
3+
import scala.annotation.tailrec
4+
import scala.util.Random
5+
6+
/// randomized QuickSelect searches the k-smallest Number in a set of unordered numbers
7+
/// This is a more generalized approach to the Median search for QuickSort
8+
///
9+
/// Due to a randomized pick of the pivot, the Runtime is O(n):
10+
/// T(n) <= T(3/4 * n) + O(n)
11+
12+
@tailrec
13+
def quickSelect(A: Array[Int], k: Int): Int = {
14+
require(A.nonEmpty, "Array must not be empty")
15+
if (A.length == 1) return A(0)
16+
17+
// Random pivot selection
18+
val pivot = A(Random.nextInt(A.length))
19+
20+
// Partition elements efficient with Lists
21+
val (less, equal, greater) = A.foldLeft((List[Int](), List[Int](), List[Int]())) {
22+
case ((less, equal, greater), elem) =>
23+
if (elem < pivot) (elem :: less, equal, greater)
24+
else if (elem > pivot) (less, equal, elem :: greater)
25+
else (less, elem :: equal, greater)
26+
}
27+
28+
if (less.length >= k) {
29+
quickSelect(less.toArray, k)
30+
} else if (less.length + equal.length >= k) {
31+
pivot
32+
} else {
33+
quickSelect(greater.toArray, k - less.length - equal.length)
34+
}
35+
}

src/main/scala/algorithms/sort/RadixSort.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ package algorithms.sort
66
/// Example:
77
/// val Arr = Array(170, 45, 75, 90, 802, 24, 2, 66)
88
/// val result = radixSort(Arr, 3)
9-
/// println(result.mkString(", ")) // Returns Array(2, 24, 45, 66, 75, 90, 170, 802)
9+
/// println(result) // Returns Array(2, 24, 45, 66, 75, 90, 170, 802)
1010

1111
def radixSort(A: Array[Int], d: Int): Array[Int] = {
1212
var output = A.clone()
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package algorithms.sort
2+
3+
import algorithms.search.quickSelect
4+
import algorithms.search.MdMSelect
5+
6+
/// Randomized QuickSort implementation using QuickSelect for pivot selection
7+
/// This achieves an expected runtime of O(n log n) due to the randomized pivot.
8+
/// The worst-case runtime remains O(n^2), but it is highly unlikely in practice
9+
///
10+
/// The algorithm partitions the array around the pivot and recursively sorts both halves.
11+
def randomizedQuickSort(A: Array[Int]): Array[Int] = {
12+
if (A.length < 2) return A
13+
14+
val p: Int = quickSelect(A, A.length / 2) // Randomized pivot selection
15+
16+
val (less, equal, greater) = A.foldLeft((List[Int](), List[Int](), List[Int]())) {
17+
case ((less, equal, greater), elem) =>
18+
if (elem < p) (elem :: less, equal, greater)
19+
else if (elem > p) (less, equal, elem :: greater)
20+
else (less, elem :: equal, greater)
21+
}
22+
23+
randomizedQuickSort(less.toArray) ++ equal ++ randomizedQuickSort(greater.toArray)
24+
}
25+
26+
/// Deterministic QuickSort implementation using the Median of Medians pivot selection
27+
/// This guarantees an O(n log n) runtime in all cases, including the worst-case scenario.
28+
///
29+
/// The pivot is selected deterministically via MdMSelect, making this more robust
30+
/// than the randomized version for adversarial inputs.
31+
def deterministicQuickSort(A: Array[Int]): Array[Int] = {
32+
if (A.length < 2) return A
33+
34+
val p: Int = MdMSelect(A, A.length / 2) // Deterministic pivot selection
35+
36+
val (less, equal, greater) = A.foldLeft((List[Int](), List[Int](), List[Int]())) {
37+
case ((less, equal, greater), elem) =>
38+
if (elem < p) (elem :: less, equal, greater)
39+
else if (elem > p) (less, equal, elem :: greater)
40+
else (less, elem :: equal, greater)
41+
}
42+
43+
deterministicQuickSort(less.toArray) ++ equal ++ deterministicQuickSort(greater.toArray)
44+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package algorithms.sort
2+
3+
import munit.FunSuite
4+
import algorithms.sort.randomizedQuickSort
5+
import algorithms.sort.deterministicQuickSort
6+
7+
class improvedQuickSortTest extends FunSuite {
8+
test("randomizedQuickSort sorts arrays correctly") {
9+
val arr = Array(4, 2, 1, 5, 6, 3, 7, 12, 8, 10, 9, 11)
10+
val res = randomizedQuickSort(arr)
11+
assertEquals(res.toSeq, Array.range(1, 13).toSeq)
12+
}
13+
test("randomizedQuickSort sorts arrays with negatives correctly") {
14+
val arr = Array(-1, 4, 2, 1, -4, 5, 6, 3, 0, 7, 12, -3, 8, 10, 9, -2, 11)
15+
val res = randomizedQuickSort(arr)
16+
assertEquals(res.toSeq, Array.range(-4, 13).toSeq)
17+
}
18+
19+
test("randomizedQuickSort sorts arrays with duplicates") {
20+
val arr = Array(4, 2, 1, 5, 3, 2)
21+
val res = randomizedQuickSort(arr)
22+
assertEquals(res.toSeq, Array(1, 2, 2, 3, 4, 5).toSeq)
23+
}
24+
25+
test("randomizedQuickSort handles singleton arrays") {
26+
val arr = Array(1)
27+
randomizedQuickSort(arr)
28+
assertEquals(arr.toSeq, Array(1).toSeq)
29+
}
30+
test("randomizedQuickSort-sort handles empty arrays") {
31+
val arr = Array.empty[Int]
32+
randomizedQuickSort(arr)
33+
assertEquals(arr.toSeq, Array.empty[Int].toSeq)
34+
}
35+
36+
class deterministicQuickSortTest extends FunSuite {
37+
test("deterministicQuickSort sorts arrays correctly") {
38+
val arr = Array(4, 2, 1, 5, 6, 3, 7, 12, 8, 10, 9, 11)
39+
deterministicQuickSort(arr)
40+
assertEquals(arr.toSeq, Array.range(1, 13).toSeq)
41+
}
42+
test("deterministicQuickSort sorts arrays with negatives correctly") {
43+
val arr = Array(-1, 4, 2, 1, -4, 5, 6, 3, 0, 7, 12, -3, 8, 10, 9, -2, 11)
44+
deterministicQuickSort(arr)
45+
assertEquals(arr.toSeq, Array.range(-4, 13).toSeq)
46+
}
47+
48+
test("deterministicQuickSort sorts arrays with duplicates") {
49+
val arr = Array(4, 2, 1, 5, 3, 2)
50+
deterministicQuickSort(arr)
51+
assertEquals(arr.toSeq, Array(1, 2, 2, 3, 4, 5).toSeq)
52+
}
53+
54+
test("deterministicQuickSort handles singleton arrays") {
55+
val arr = Array(1)
56+
deterministicQuickSort(arr)
57+
assertEquals(arr.toSeq, Array(1).toSeq)
58+
}
59+
test("deterministicQuickSort-sort handles empty arrays") {
60+
val arr = Array.empty[Int]
61+
deterministicQuickSort(arr)
62+
assertEquals(arr.toSeq, Array.empty[Int].toSeq)
63+
}
64+
65+
}
66+
}

0 commit comments

Comments
 (0)