mirror of
https://github.com/TheAlgorithms/Java.git
synced 2025-07-06 17:29:31 +08:00
Add Quick Select (#2860)
Co-authored-by: Nils Goldmann <goldman09@mi.fu-berlin.de> Co-authored-by: Andrii Siriak <siryaka@gmail.com>
This commit is contained in:
37
pom.xml
37
pom.xml
@ -1,5 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>com.thealgorithms</groupId>
|
<groupId>com.thealgorithms</groupId>
|
||||||
<artifactId>Java</artifactId>
|
<artifactId>Java</artifactId>
|
||||||
@ -10,4 +12,37 @@
|
|||||||
<maven.compiler.source>17</maven.compiler.source>
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
<maven.compiler.target>17</maven.compiler.target>
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit</groupId>
|
||||||
|
<artifactId>junit-bom</artifactId>
|
||||||
|
<version>5.8.2</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.8.1</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>2.22.2</version>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
</project>
|
</project>
|
140
src/main/java/com/thealgorithms/searches/QuickSelect.java
Normal file
140
src/main/java/com/thealgorithms/searches/QuickSelect.java
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
package com.thealgorithms.searches;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of the Quickselect algorithm as described
|
||||||
|
* <a href="https://en.wikipedia.org/wiki/Median_of_medians">here</a>.
|
||||||
|
*/
|
||||||
|
public final class QuickSelect {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects the {@code n}-th largest element of {@code list}, i.e. the element that would
|
||||||
|
* be at index n if the list was sorted.
|
||||||
|
* <p>
|
||||||
|
* Calling this function might change the order of elements in {@code list}.
|
||||||
|
*
|
||||||
|
* @param list the list of elements
|
||||||
|
* @param n the index
|
||||||
|
* @param <T> the type of list elements
|
||||||
|
* @return the n-th largest element in the list
|
||||||
|
* @throws IndexOutOfBoundsException if n is less than 0 or greater or equal to
|
||||||
|
* the number of elements in the list
|
||||||
|
* @throws IllegalArgumentException if the list is empty
|
||||||
|
* @throws NullPointerException if {@code list} is null
|
||||||
|
*/
|
||||||
|
public static <T extends Comparable<T>> T select(List<T> list, int n) {
|
||||||
|
Objects.requireNonNull(list, "The list of elements must not be null.");
|
||||||
|
|
||||||
|
if (list.size() == 0) {
|
||||||
|
String msg = "The list of elements must not be empty.";
|
||||||
|
throw new IllegalArgumentException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n < 0) {
|
||||||
|
String msg = "The index must not be negative.";
|
||||||
|
throw new IndexOutOfBoundsException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n >= list.size()) {
|
||||||
|
String msg = "The index must be less than the number of elements.";
|
||||||
|
throw new IndexOutOfBoundsException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = selectIndex(list, n);
|
||||||
|
return list.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T extends Comparable<T>> int selectIndex(List<T> list, int n) {
|
||||||
|
return selectIndex(list, 0, list.size() - 1, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T extends Comparable<T>> int selectIndex(
|
||||||
|
List<T> list,
|
||||||
|
int left,
|
||||||
|
int right,
|
||||||
|
int n
|
||||||
|
) {
|
||||||
|
while (true) {
|
||||||
|
if (left == right)
|
||||||
|
return left;
|
||||||
|
int pivotIndex = pivot(list, left, right);
|
||||||
|
pivotIndex = partition(list, left, right, pivotIndex, n);
|
||||||
|
if (n == pivotIndex) {
|
||||||
|
return n;
|
||||||
|
} else if (n < pivotIndex) {
|
||||||
|
right = pivotIndex - 1;
|
||||||
|
} else {
|
||||||
|
left = pivotIndex + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T extends Comparable<T>> int partition(
|
||||||
|
List<T> list,
|
||||||
|
int left,
|
||||||
|
int right,
|
||||||
|
int pivotIndex,
|
||||||
|
int n
|
||||||
|
) {
|
||||||
|
T pivotValue = list.get(pivotIndex);
|
||||||
|
Collections.swap(list, pivotIndex, right);
|
||||||
|
int storeIndex = left;
|
||||||
|
|
||||||
|
for (int i = left; i < right; i++) {
|
||||||
|
if (list.get(i).compareTo(pivotValue) < 0) {
|
||||||
|
Collections.swap(list, storeIndex, i);
|
||||||
|
storeIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int storeIndexEq = storeIndex;
|
||||||
|
|
||||||
|
for (int i = storeIndex; i < right; i++) {
|
||||||
|
if (list.get(i).compareTo(pivotValue) == 0) {
|
||||||
|
Collections.swap(list, storeIndexEq, i);
|
||||||
|
storeIndexEq++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.swap(list, right, storeIndexEq);
|
||||||
|
|
||||||
|
return (n < storeIndex)
|
||||||
|
? storeIndex
|
||||||
|
: Math.min(n, storeIndexEq);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T extends Comparable<T>> int pivot(
|
||||||
|
List<T> list,
|
||||||
|
int left,
|
||||||
|
int right
|
||||||
|
) {
|
||||||
|
if (right - left < 5) {
|
||||||
|
return partition5(list, left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = left; i < right; i += 5) {
|
||||||
|
int subRight = i + 4;
|
||||||
|
if (subRight > right) {
|
||||||
|
subRight = right;
|
||||||
|
}
|
||||||
|
int median5 = partition5(list, i, subRight);
|
||||||
|
int rightIndex = left + (i - left) / 5;
|
||||||
|
Collections.swap(list, median5, rightIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
int mid = (right - left) / 10 + left + 1;
|
||||||
|
int rightIndex = left + (right - left) / 5;
|
||||||
|
return selectIndex(list, left, rightIndex, mid);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T extends Comparable<T>> int partition5(
|
||||||
|
List<T> list,
|
||||||
|
int left,
|
||||||
|
int right
|
||||||
|
) {
|
||||||
|
List<T> ts = list.subList(left, right);
|
||||||
|
ts.sort(Comparator.naturalOrder());
|
||||||
|
return (left + right) >>> 1;
|
||||||
|
}
|
||||||
|
}
|
240
src/test/java/com/thealgorithms/searches/QuickSelectTest.java
Normal file
240
src/test/java/com/thealgorithms/searches/QuickSelectTest.java
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
package com.thealgorithms.searches;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class QuickSelectTest {
|
||||||
|
@Test
|
||||||
|
void quickSelectMinimumOfOneElement() {
|
||||||
|
List<Integer> elements = Collections.singletonList(42);
|
||||||
|
int minimum = QuickSelect.select(elements, 0);
|
||||||
|
assertEquals(42, minimum);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void quickSelectMinimumOfTwoElements() {
|
||||||
|
List<Integer> elements1 = Arrays.asList(42, 90);
|
||||||
|
List<Integer> elements2 = Arrays.asList(90, 42);
|
||||||
|
|
||||||
|
int minimum1 = QuickSelect.select(elements1, 0);
|
||||||
|
int minimum2 = QuickSelect.select(elements2, 0);
|
||||||
|
|
||||||
|
assertEquals(42, minimum1);
|
||||||
|
assertEquals(42, minimum2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void quickSelectMinimumOfThreeElements() {
|
||||||
|
List<Integer> elements1 = Arrays.asList(1, 2, 3);
|
||||||
|
List<Integer> elements2 = Arrays.asList(2, 1, 3);
|
||||||
|
List<Integer> elements3 = Arrays.asList(2, 3, 1);
|
||||||
|
|
||||||
|
int minimum1 = QuickSelect.select(elements1, 0);
|
||||||
|
int minimum2 = QuickSelect.select(elements2, 0);
|
||||||
|
int minimum3 = QuickSelect.select(elements3, 0);
|
||||||
|
|
||||||
|
assertEquals(1, minimum1);
|
||||||
|
assertEquals(1, minimum2);
|
||||||
|
assertEquals(1, minimum3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void quickSelectMinimumOfManyElements() {
|
||||||
|
List<Integer> elements = generateRandomIntegers(NUM_RND_ELEMENTS);
|
||||||
|
int actual = QuickSelect.select(elements, 0);
|
||||||
|
int expected = elements.stream().min(Comparator.naturalOrder()).get();
|
||||||
|
assertEquals(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void quickSelectMaximumOfOneElement() {
|
||||||
|
List<Integer> elements = Collections.singletonList(42);
|
||||||
|
int maximum = QuickSelect.select(elements, 0);
|
||||||
|
assertEquals(42, maximum);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void quickSelectMaximumOfTwoElements() {
|
||||||
|
List<Integer> elements1 = Arrays.asList(42, 90);
|
||||||
|
List<Integer> elements2 = Arrays.asList(90, 42);
|
||||||
|
|
||||||
|
int maximum1 = QuickSelect.select(elements1, 1);
|
||||||
|
int maximum2 = QuickSelect.select(elements2, 1);
|
||||||
|
|
||||||
|
assertEquals(90, maximum1);
|
||||||
|
assertEquals(90, maximum2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void quickSelectMaximumOfThreeElements() {
|
||||||
|
List<Integer> elements1 = Arrays.asList(1, 2, 3);
|
||||||
|
List<Integer> elements2 = Arrays.asList(2, 1, 3);
|
||||||
|
List<Integer> elements3 = Arrays.asList(2, 3, 1);
|
||||||
|
|
||||||
|
int maximum1 = QuickSelect.select(elements1, 2);
|
||||||
|
int maximum2 = QuickSelect.select(elements2, 2);
|
||||||
|
int maximum3 = QuickSelect.select(elements3, 2);
|
||||||
|
|
||||||
|
assertEquals(3, maximum1);
|
||||||
|
assertEquals(3, maximum2);
|
||||||
|
assertEquals(3, maximum3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void quickSelectMaximumOfManyElements() {
|
||||||
|
List<Integer> elements = generateRandomIntegers(NUM_RND_ELEMENTS);
|
||||||
|
int actual = QuickSelect.select(elements, NUM_RND_ELEMENTS - 1);
|
||||||
|
int expected = elements.stream().max(Comparator.naturalOrder()).get();
|
||||||
|
assertEquals(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void quickSelectMedianOfOneElement() {
|
||||||
|
List<Integer> elements = Collections.singletonList(42);
|
||||||
|
int median = QuickSelect.select(elements, 0);
|
||||||
|
assertEquals(42, median);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void quickSelectMedianOfThreeElements() {
|
||||||
|
List<Integer> elements1 = Arrays.asList(1, 2, 3);
|
||||||
|
List<Integer> elements2 = Arrays.asList(2, 1, 3);
|
||||||
|
List<Integer> elements3 = Arrays.asList(2, 3, 1);
|
||||||
|
|
||||||
|
int median1 = QuickSelect.select(elements1, 1);
|
||||||
|
int median2 = QuickSelect.select(elements2, 1);
|
||||||
|
int median3 = QuickSelect.select(elements3, 1);
|
||||||
|
|
||||||
|
assertEquals(2, median1);
|
||||||
|
assertEquals(2, median2);
|
||||||
|
assertEquals(2, median3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void quickSelectMedianOfManyElements() {
|
||||||
|
int medianIndex = NUM_RND_ELEMENTS / 2;
|
||||||
|
List<Integer> elements = generateRandomIntegers(NUM_RND_ELEMENTS);
|
||||||
|
int actual = QuickSelect.select(elements, medianIndex);
|
||||||
|
|
||||||
|
List<Integer> elementsSorted = getSortedCopyOfList(elements);
|
||||||
|
assertEquals(elementsSorted.get(medianIndex), actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void quickSelect30thPercentileOf10Elements() {
|
||||||
|
List<Integer> elements = generateRandomIntegers(10);
|
||||||
|
int actual = QuickSelect.select(elements, 2);
|
||||||
|
|
||||||
|
List<Integer> elementsSorted = getSortedCopyOfList(elements);
|
||||||
|
assertEquals(elementsSorted.get(2), actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void quickSelect30thPercentileOfManyElements() {
|
||||||
|
int percentile30th = NUM_RND_ELEMENTS / 10 * 3;
|
||||||
|
List<Integer> elements = generateRandomIntegers(NUM_RND_ELEMENTS);
|
||||||
|
int actual = QuickSelect.select(elements, percentile30th);
|
||||||
|
|
||||||
|
List<Integer> elementsSorted = getSortedCopyOfList(elements);
|
||||||
|
assertEquals(elementsSorted.get(percentile30th), actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void quickSelect70thPercentileOf10Elements() {
|
||||||
|
List<Integer> elements = generateRandomIntegers(10);
|
||||||
|
int actual = QuickSelect.select(elements, 6);
|
||||||
|
|
||||||
|
List<Integer> elementsSorted = getSortedCopyOfList(elements);
|
||||||
|
assertEquals(elementsSorted.get(6), actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void quickSelect70thPercentileOfManyElements() {
|
||||||
|
int percentile70th = NUM_RND_ELEMENTS / 10 * 7;
|
||||||
|
List<Integer> elements = generateRandomIntegers(NUM_RND_ELEMENTS);
|
||||||
|
int actual = QuickSelect.select(elements, percentile70th);
|
||||||
|
|
||||||
|
List<Integer> elementsSorted = getSortedCopyOfList(elements);
|
||||||
|
assertEquals(elementsSorted.get(percentile70th), actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void quickSelectMedianOfThreeCharacters() {
|
||||||
|
List<Character> elements = Arrays.asList('X', 'Z', 'Y');
|
||||||
|
char actual = QuickSelect.select(elements, 1);
|
||||||
|
assertEquals(actual, 'Y');
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void quickSelectMedianOfManyCharacters() {
|
||||||
|
List<Character> elements = generateRandomCharacters(NUM_RND_ELEMENTS);
|
||||||
|
char actual = QuickSelect.select(elements, NUM_RND_ELEMENTS / 30);
|
||||||
|
|
||||||
|
List<Character> elementsSorted = getSortedCopyOfList(elements);
|
||||||
|
assertEquals(elementsSorted.get(NUM_RND_ELEMENTS / 30), actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void quickSelectNullList() {
|
||||||
|
NullPointerException exception = assertThrows(
|
||||||
|
NullPointerException.class,
|
||||||
|
() -> QuickSelect.select(null, 0)
|
||||||
|
);
|
||||||
|
String expectedMsg = "The list of elements must not be null.";
|
||||||
|
assertEquals(expectedMsg, exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void quickSelectEmptyList() {
|
||||||
|
List<String> objects = Collections.emptyList();
|
||||||
|
IllegalArgumentException exception = assertThrows(
|
||||||
|
IllegalArgumentException.class,
|
||||||
|
() -> QuickSelect.select(objects, 0)
|
||||||
|
);
|
||||||
|
String expectedMsg = "The list of elements must not be empty.";
|
||||||
|
assertEquals(expectedMsg, exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void quickSelectIndexOutOfLeftBound() {
|
||||||
|
IndexOutOfBoundsException exception = assertThrows(
|
||||||
|
IndexOutOfBoundsException.class,
|
||||||
|
() -> QuickSelect.select(Collections.singletonList(1), -1)
|
||||||
|
);
|
||||||
|
String expectedMsg = "The index must not be negative.";
|
||||||
|
assertEquals(expectedMsg, exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void quickSelectIndexOutOfRightBound() {
|
||||||
|
IndexOutOfBoundsException exception = assertThrows(
|
||||||
|
IndexOutOfBoundsException.class,
|
||||||
|
() -> QuickSelect.select(Collections.singletonList(1), 1)
|
||||||
|
);
|
||||||
|
String expectedMsg = "The index must be less than the number of elements.";
|
||||||
|
assertEquals(expectedMsg, exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int NUM_RND_ELEMENTS = 99;
|
||||||
|
private static final Random RANDOM = new Random(42);
|
||||||
|
private static final int ASCII_A = 0x41;
|
||||||
|
private static final int ASCII_Z = 0x5A;
|
||||||
|
|
||||||
|
private static List<Integer> generateRandomIntegers(int n) {
|
||||||
|
return RANDOM.ints(n).boxed().collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Character> generateRandomCharacters(int n) {
|
||||||
|
return RANDOM.ints(n, ASCII_A, ASCII_Z)
|
||||||
|
.mapToObj(i -> (char) i)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T extends Comparable<T>> List<T> getSortedCopyOfList(List<T> list) {
|
||||||
|
return list.stream().sorted().collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user