So finden Sie das Kth größte Element in Java

So finden Sie das k-te größte Element in Java

1. Einführung

In diesem Artikel werden verschiedene Lösungen vorgestellt, um dasketw größte Element in einer Folge eindeutiger Zahlen zu finden. Für unsere Beispiele verwenden wir eine Reihe von Ganzzahlen.

Wir werden auch über die durchschnittliche und Worst-Case-Zeitkomplexität jedes Algorithmus sprechen.

2. Lösungen

Lassen Sie uns nun einige mögliche Lösungen untersuchen - eine mit einer einfachen Sortierung und zwei mit dem von Quick Sort abgeleiteten Quick Select-Algorithmus.

2.1. Sortierung

Wenn wir über das Problem nachdenken, vielleichtthe most obvious solution that comes to mind isto sort the array.

Definieren wir die erforderlichen Schritte:

  • Sortieren Sie das Array in aufsteigender Reihenfolge

  • Da das letzte Element des Arrays das größte Element wäre, würde dasketw größte Element beixth Index liegen, wobeix = length(array) – k

Wie wir sehen können, ist die Lösung einfach, erfordert jedoch das Sortieren des gesamten Arrays. Daher beträgt die zeitliche KomplexitätO(n*logn):

public int findKthLargestBySorting(Integer[] arr, int k) {
    Arrays.sort(arr);
    int targetIndex = arr.length - k;
    return arr[targetIndex];
}

Ein alternativer Ansatz besteht darin, das Array in absteigender Reihenfolge zu sortieren und das Element einfach auf(k-1)etw Index zurückzugeben:

public int findKthLargestBySortingDesc(Integer[] arr, int k) {
    Arrays.sort(arr, Collections.reverseOrder());
    return arr[k-1];
}

2.2. Schnellauswahl

Dies kann als Optimierung des bisherigen Ansatzes angesehen werden. Dabei wählen wir dieQuickSort zum Sortieren aus. Bei der Analyse der Problemstellung stellen wir fest, dasswe don’t actually need to sort the entire array — we only need to rearrange its contents so that the kth element of the array is the kth largest or smallest.

In QuickSort wählen wir ein Pivot-Element aus und verschieben es in die richtige Position. Wir partitionieren auch das Array darum herum. In QuickSelect, the idea is to stop at the point where the pivot itself is the kth largest element.

Wir können den Algorithmus weiter optimieren, wenn wir nicht sowohl für die linke als auch für die rechte Seite des Pivots erneut auftreten. Wir müssen nur eine davon entsprechend der Position des Drehpunkts wiederholen.

Schauen wir uns die Grundideen des QuickSelect-Algorithmus an:

  • Wählen Sie ein Pivot-Element und unterteilen Sie das Array entsprechend

    • Wählen Sie das Element ganz rechts als Drehpunkt

    • Mische das Array neu, sodass das Pivot-Element an seiner richtigen Stelle platziert wird - alle Elemente, die kleiner als der Pivot sind, befinden sich an niedrigeren Indizes, und Elemente, die größer als der Pivot sind, befinden sich an höheren Indizes als der Pivot

  • Wenn der Pivot amketw-Element im Array platziert ist, beenden Sie den Prozess, da der Pivot dasketw größte Element ist

  • Wenn die Schwenkposition größer alsk, ist, setzen Sie den Vorgang mit dem linken Subarray fort. Andernfalls wiederholen Sie den Vorgang mit dem rechten Subarray

Wir können eine generische Logik schreiben, mit der auch das kleinste Elementketw gefunden werden kann. Wir definieren eine MethodefindKthElementByQuickSelect(), die daskth-Element im sortierten Array zurückgibt.

Wenn wir das Array in aufsteigender Reihenfolge sortieren, ist dasketw-Element eines Arrays dasketw kleinste Element. Um dasketw größte Element zu finden, können wirk= length(Array) – k. übergeben

Implementieren wir diese Lösung:

public int
  findKthElementByQuickSelect(Integer[] arr, int left, int right, int k) {
    if (k >= 0 && k <= right - left + 1) {
        int pos = partition(arr, left, right);
        if (pos - left == k) {
            return arr[pos];
        }
        if (pos - left > k) {
            return findKthElementByQuickSelect(arr, left, pos - 1, k);
        }
        return findKthElementByQuickSelect(arr, pos + 1,
          right, k - pos + left - 1);
    }
    return 0;
}

Implementieren wir nun die Methodepartition, bei der das am weitesten rechts stehende Element als Pivot ausgewählt, am entsprechenden Index platziert und das Array so partitioniert wird, dass Elemente mit niedrigeren Indizes kleiner als das Pivot-Element sein sollten.

In ähnlicher Weise sind Elemente mit höheren Indizes größer als das Pivot-Element:

public int partition(Integer[] arr, int left, int right) {
    int pivot = arr[right];
    Integer[] leftArr;
    Integer[] rightArr;

    leftArr = IntStream.range(left, right)
      .filter(i -> arr[i] < pivot)
      .map(i -> arr[i])
      .boxed()
      .toArray(Integer[]::new);

    rightArr = IntStream.range(left, right)
      .filter(i -> arr[i] > pivot)
      .map(i -> arr[i])
      .boxed()
      .toArray(Integer[]::new);

    int leftArraySize = leftArr.length;
    System.arraycopy(leftArr, 0, arr, left, leftArraySize);
    arr[leftArraySize+left] = pivot;
    System.arraycopy(rightArr, 0, arr, left + leftArraySize + 1,
      rightArr.length);

    return left + leftArraySize;
}

Es gibt einen einfacheren, iterativen Ansatz, um die Partitionierung zu erreichen:

public int partitionIterative(Integer[] arr, int left, int right) {
    int pivot = arr[right], i = left;
    for (int j = left; j <= right - 1; j++) {
        if (arr[j] <= pivot) {
            swap(arr, i, j);
            i++;
        }
    }
    swap(arr, i, right);
    return i;
}

public void swap(Integer[] arr, int n1, int n2) {
    int temp = arr[n2];
    arr[n2] = arr[n1];
    arr[n1] = temp;
}

Diese Lösung arbeitet durchschnittlich inO(n) Zeit. Im schlimmsten Fall beträgt die Zeitkomplexität jedochO(n^2).

2.3. QuickSelect mit zufälliger Partition

Dieser Ansatz ist eine geringfügige Modifikation des vorherigen Ansatzes. Wenn das Array fast / vollständig sortiert ist und das Element ganz rechts als Drehpunkt ausgewählt wird, ist die Aufteilung der linken und rechten Subarrays sehr ungleichmäßig.

Diese Methode schlägtpicking the initial pivot element in a random manner.vor. Wir müssen die Partitionierungslogik jedoch nicht ändern.

Anstattpartition aufzurufen, rufen wir die MethoderandomPartition auf, die ein zufälliges Element auswählt und mit dem Element ganz rechts austauscht, bevor schließlich die Methodepartition aufgerufen wird.

Implementieren wir dierandomPartition-Methode:

public int randomPartition(Integer arr[], int left, int right) {
    int n = right - left + 1;
    int pivot = (int) (Math.random()) * n;
    swap(arr, left + pivot, right);
    return partition(arr, left, right);
}

Diese Lösung funktioniert in den meisten Fällen besser als der vorherige Fall.

Die erwartete zeitliche Komplexität von randomisiertem QuickSelect beträgtO(n).

Die schlechteste Zeitkomplexität bleibt jedoch immer nochO(n^2).

3. Fazit

In diesem Artikel diskutierten wir verschiedene Lösungen, um daskth größte (oder kleinste) Element in einem Array eindeutiger Zahlen zu finden. Die einfachste Lösung besteht darin, das Array zu sortieren und daskth-Element zurückzugeben. Diese Lösung hat eine Zeitkomplexität vonO(n*logn).

Wir haben auch zwei Varianten von Quick Select besprochen. Dieser Algorithmus ist nicht einfach, hat aber im Durchschnitt eine Zeitkomplexität vonO(n).

Wie immer kann der vollständige Code für den Algorithmus inover on GitHub gefunden werden.