Wie finde ich das k-te größte Element in einem unsortierten Array der Länge n in O (n)?


220

Ich glaube, es gibt eine Möglichkeit, das k-te größte Element in einem unsortierten Array der Länge n in O (n) zu finden. Oder vielleicht ist es "erwartet" O (n) oder so. Wie können wir das machen?


49
Übrigens verwandelt sich so ziemlich jeder hier beschriebene Algorithmus in O (n ^ 2) oder O (n log n), wenn k == n ist. Das heißt, ich glaube nicht, dass einer von ihnen für alle Werte von k O (n) ist. Ich wurde modifiziert, um darauf hinzuweisen, dachte aber, du solltest es trotzdem wissen.
Kirk Strauser

19
Auswahlalgorithmen können für jeden festen Wert von k O (n) sein. Das heißt, Sie können einen Auswahlalgorithmus für k = 25 haben, der O (n) für jeden Wert von n ist, und Sie können dies für jeden bestimmten Wert von k tun, der nicht mit n zusammenhängt. Der Fall, in dem der Algorithmus nicht mehr O (n) ist, ist, wenn der Wert von k eine gewisse Abhängigkeit vom Wert von n aufweist, wie z. B. k = n oder k = n / 2. Dies bedeutet jedoch nicht, dass, wenn Sie den k = 25-Algorithmus auf einer Liste von 25 Elementen ausführen, dieser plötzlich nicht mehr O (n) ist, da die O-Notation eine Eigenschaft des Algorithmus beschreibt, keine bestimmte Lauf davon.
Tyler McHenry

1
Diese Frage wurde mir in einem Amazon-Interview gestellt, um das zweitgrößte Element zu finden. Übrigens, der Interviewer hat das Interview geführt. Ich habe nicht gefragt, ob ich das ursprüngliche Array zerstören (dh sortieren) könnte, also habe ich eine komplizierte Lösung gefunden.
Sambatyon

4
Dies ist Frage 9 in Spalte 11 (Sortieren) der Programmierperlen von Jon Bentley.
Qiang Xu

3
@ KirkStrauser: Wenn k == n oder k == n-1 ist, wird es trivial. Wir können max oder 2nd max in Single Traversal bekommen. Die hier bereitgestellten Algorithmen werden praktisch für Werte von k verwendet, die nicht zu {1,2, n-1, n} gehören
Aditya Joshee

Antworten:


173

Dies wird als Finden der Statistik der k-ten Ordnung bezeichnet . Es gibt einen sehr einfachen randomisierten Algorithmus ( Quickselect genannt ), der die O(n)durchschnittliche Zeit im O(n^2)ungünstigsten Fall benötigt, und einen ziemlich komplizierten nicht randomisierten Algorithmus ( Introselect genannt ), der die O(n)Worst-Case-Zeit benötigt. Es gibt einige Informationen auf Wikipedia , aber es ist nicht sehr gut.

Alles, was Sie brauchen, finden Sie in diesen PowerPoint-Folien . Nur um den O(n)Basisalgorithmus des Worst-Case-Algorithmus zu extrahieren (Introselect):

Select(A,n,i):
    Divide input into ⌈n/5⌉ groups of size 5.

    /* Partition on median-of-medians */
    medians = array of each group’s median.
    pivot = Select(medians, ⌈n/5⌉, ⌈n/10⌉)
    Left Array L and Right Array G = partition(A, pivot)

    /* Find ith element in L, pivot, or G */
    k = |L| + 1
    If i = k, return pivot
    If i < k, return Select(L, k-1, i)
    If i > k, return Select(G, n-k, i-k)

Es ist auch sehr schön im Buch Einführung in Algorithmen von Cormen et al.


6
Danke für die Folien.
Kshitij Banerjee

5
Warum muss es in Größe 5 funktionieren? Warum funktioniert es nicht mit Größe 3?
Joffrey Baratheon

11
@eladv Der Folienlink ist defekt :(
Misha Moroshko

7
@eladv Bitte beheben Sie den defekten Link.
Maxx777

1
@ MischaMoroshko Link ist behoben
Alfasin

118

Wenn Sie O(n)im Gegensatz zu O(kn)oder so etwas einen echten Algorithmus wollen , sollten Sie quickselect verwenden (es ist im Grunde eine Quicksortierung, bei der Sie die Partition wegwerfen, an der Sie nicht interessiert sind). Mein Professor hat eine großartige Zusammenfassung mit der Laufzeitanalyse: ( Referenz )

Der QuickSelect-Algorithmus findet schnell das k-te kleinste Element eines unsortierten Arrays von nElementen. Da es sich um einen RandomizedAlgorithmus handelt , berechnen wir die im schlimmsten Fall erwartete Laufzeit.

Hier ist der Algorithmus.

QuickSelect(A, k)
  let r be chosen uniformly at random in the range 1 to length(A)
  let pivot = A[r]
  let A1, A2 be new arrays
  # split into a pile A1 of small elements and A2 of big elements
  for i = 1 to n
    if A[i] < pivot then
      append A[i] to A1
    else if A[i] > pivot then
      append A[i] to A2
    else
      # do nothing
  end for
  if k <= length(A1):
    # it's in the pile of small elements
    return QuickSelect(A1, k)
  else if k > length(A) - length(A2)
    # it's in the pile of big elements
    return QuickSelect(A2, k - (length(A) - length(A2))
  else
    # it's equal to the pivot
    return pivot

Was ist die Laufzeit dieses Algorithmus? Wenn der Gegner Münzen für uns wirft, stellen wir möglicherweise fest, dass der Drehpunkt immer das größte Element ist und kimmer 1 ist, was eine Laufzeit von ergibt

T(n) = Theta(n) + T(n-1) = Theta(n2)

Wenn die Auswahl jedoch tatsächlich zufällig ist, ist die erwartete Laufzeit gegeben durch

T(n) <= Theta(n) + (1/n) ∑i=1 to nT(max(i, n-i-1))

wo wir die nicht ganz vernünftige Annahme machen, dass die Rekursion immer im größeren von A1oder landet A2.

Lassen Sie uns das T(n) <= anfür einige erraten a. Dann bekommen wir

T(n) 
 <= cn + (1/n) ∑i=1 to nT(max(i-1, n-i))
 = cn + (1/n) ∑i=1 to floor(n/2) T(n-i) + (1/n) ∑i=floor(n/2)+1 to n T(i)
 <= cn + 2 (1/n) ∑i=floor(n/2) to n T(i)
 <= cn + 2 (1/n) ∑i=floor(n/2) to n ai

und jetzt müssen wir irgendwie die schreckliche Summe rechts vom Pluszeichen bekommen, um cndie links zu absorbieren . Wenn wir es nur so binden , bekommen wir ungefähr . Aber das ist zu groß - es gibt keinen Platz, um ein Extra einzudrücken . Erweitern wir also die Summe mit der Formel der arithmetischen Reihen:2(1/n) ∑i=n/2 to n an2(1/n)(n/2)an = ancn

i=floor(n/2) to n i  
 = ∑i=1 to n i - ∑i=1 to floor(n/2) i  
 = n(n+1)/2 - floor(n/2)(floor(n/2)+1)/2  
 <= n2/2 - (n/4)2/2  
 = (15/32)n2

wo wir ausnutzen, dass n "ausreichend groß" ist, um die hässlichen floor(n/2)Faktoren durch die viel saubereren (und kleineren) zu ersetzen n/4. Jetzt können wir weitermachen

cn + 2 (1/n) ∑i=floor(n/2) to n ai,
 <= cn + (2a/n) (15/32) n2
 = n (c + (15/16)a)
 <= an

zur Verfügung gestellt a > 16c.

Das gibt T(n) = O(n). Es ist klar Omega(n), also bekommen wir T(n) = Theta(n).


12
Die Schnellauswahl ist im Durchschnitt nur O (n). Der Median-of-Medians-Algorithmus kann verwendet werden, um das Problem im schlimmsten Fall in O (n) -Zeit zu lösen.
John Kurlak

Was bedeutet das k > length(A) - length(A2)?
WoooHaaaa

Dies ist nicht O (n), Sie rufen die Funktion erneut als rekursiv auf, T (n). Es gibt dort bereits ein O (n) innerhalb der rekursiven Funktion T (n), so dass die Gesamtkomplexität offensichtlich ohne nachzudenken größer als O (n) wäre.
user1735921

3
@ MrROY Da wir uns Ain A1und A2um den Pivot aufgeteilt haben, wissen wir das length(A) == length(A1)+length(A2)+1. Also, k > length(A)-length(A2)ist äquivalent zu k > length(A1)+1, was wahr ist, wenn kirgendwo drin ist A2.
Filipe Gonçalves

@ FilipeGonçalves, ja, wenn der Pivot keine doppelten Elemente enthält. len (A1) + len (A2) + K-Duplikat = len (A)
d1val

16

Ein schnelles Google auf diesem ('k-ten größten Elementarray') gab Folgendes zurück: http://discuss.joelonsoftware.com/default.asp?interview.11.509587.17

"Make one pass through tracking the three largest values so far." 

(Es war speziell für 3D größte)

und diese Antwort:

Build a heap/priority queue.  O(n)
Pop top element.  O(log n)
Pop top element.  O(log n)
Pop top element.  O(log n)

Total = O(n) + 3 O(log n) = O(n)

15
Nun, es ist tatsächlich O (n) + O (k log n), was sich für signifikante Werte von K nicht verringert
Jimmy

2
Das Einfügen der Einfügemarke in diese doppelt verknüpfte Liste ist jedoch O (k).
Kirk Strauser

1
Und wenn k fest ist, ist O (k) = O (1)
Tyler McHenry

1
@ Warren: Big-O nähert sich an, aber Sie nähern sich immer an. Quicksort ist zum Beispiel tatsächlich O (n ^ 2), da dies der schlimmste Fall ist. dieser ist O (n + k log n).
Claudiu

1
Sie können k nicht als konstant behandeln. Es ist möglich, dass k = n ist. In diesem Fall ist die Zeitkomplexität O (nlogn)
Sabbir

11

Du magst Quicksort. Wähle zufällig ein Element aus und schiebe alles entweder höher oder niedriger. An diesem Punkt wissen Sie, welches Element Sie tatsächlich ausgewählt haben, und wenn es das k-te Element ist, das Sie fertig haben, wiederholen Sie andernfalls mit dem Bin (höher oder niedriger), in das das k-te Element fallen würde. Statistisch gesehen die Zeit Es dauert, bis das k-te Element mit n, O (n) wächst.


2
Das ist Quickselect, FWIW.
Rogerdpack

6

Der Begleiter eines Programmierers zur Algorithmusanalyse gibt eine Version an, die O (n) ist. Obwohl der Autor angibt, dass der konstante Faktor so hoch ist, würden Sie wahrscheinlich die naive Methode bevorzugen, die Liste zu sortieren und dann auszuwählen.

Ich habe den Brief deiner Frage beantwortet :)


2
Nicht wirklich in allen Fällen wahr. Ich habe den Median des Medians implementiert und ihn mit der in .NET integrierten Sortiermethode verglichen, und die benutzerdefinierte Lösung lief um eine Größenordnung schneller. Die eigentliche Frage ist nun: Ist Ihnen das unter bestimmten Umständen wichtig? Das Schreiben und Debuggen von 100 Codezeilen im Vergleich zu einem Liner zahlt sich nur aus, wenn dieser Code so oft ausgeführt wird, dass der Benutzer den Unterschied in der Laufzeit bemerkt und sich unwohl fühlt, wenn er auf den Abschluss des Vorgangs wartet.
Zoran Horvat

5

Die C ++ Standardbibliothek hat fast genau diese Funktion Anruf nth_element, obwohl sie Ihre Daten nicht verändert. Es hat eine lineare Laufzeit O (N) erwartet und führt auch eine Teilsortierung durch.

const int N = ...;
double a[N];
// ... 
const int m = ...; // m < N
nth_element (a, a + m, a + N);
// a[m] contains the mth element in a

1
Nein, es hat eine erwartete durchschnittliche O (n) Laufzeit. Zum Beispiel ist Quicksort im Durchschnitt O (nlogn) mit einem Worst-Case von O (n ^ 2). Wow, etwas stimmt sachlich nicht!
Kirk Strauser

5
Nein, an dieser Antwort ist sachlich nichts auszusetzen. Es funktioniert und der C ++ - Standard erfordert eine erwartete lineare Laufzeit.
David Nehme

Ich wurde im Interview gebeten, davon auszugehen, dass O (k) verfügbar ist und 'n' sehr groß ist. Ich konnte ihm keine O (n) -Lösung sagen, da ich dachte, nth_element würde Platz o (n) benötigen. Bin ich falsch? Ist der zugrunde liegende Algorithmus nicht Quicksort für nth_element?
Manish Baphna

4

Obwohl nicht sehr sicher über die Komplexität von O (n), wird es sicher zwischen O (n) und nLog (n) liegen. Achten Sie auch darauf, näher an O (n) als an nLog (n) zu sein. Die Funktion ist in Java geschrieben

public int quickSelect(ArrayList<Integer>list, int nthSmallest){
    //Choose random number in range of 0 to array length
    Random random =  new Random();
    //This will give random number which is not greater than length - 1
    int pivotIndex = random.nextInt(list.size() - 1); 

    int pivot = list.get(pivotIndex);

    ArrayList<Integer> smallerNumberList = new ArrayList<Integer>();
    ArrayList<Integer> greaterNumberList = new ArrayList<Integer>();

    //Split list into two. 
    //Value smaller than pivot should go to smallerNumberList
    //Value greater than pivot should go to greaterNumberList
    //Do nothing for value which is equal to pivot
    for(int i=0; i<list.size(); i++){
        if(list.get(i)<pivot){
            smallerNumberList.add(list.get(i));
        }
        else if(list.get(i)>pivot){
            greaterNumberList.add(list.get(i));
        }
        else{
            //Do nothing
        }
    }

    //If smallerNumberList size is greater than nthSmallest value, nthSmallest number must be in this list 
    if(nthSmallest < smallerNumberList.size()){
        return quickSelect(smallerNumberList, nthSmallest);
    }
    //If nthSmallest is greater than [ list.size() - greaterNumberList.size() ], nthSmallest number must be in this list
    //The step is bit tricky. If confusing, please see the above loop once again for clarification.
    else if(nthSmallest > (list.size() - greaterNumberList.size())){
        //nthSmallest will have to be changed here. [ list.size() - greaterNumberList.size() ] elements are already in 
        //smallerNumberList
        nthSmallest = nthSmallest - (list.size() - greaterNumberList.size());
        return quickSelect(greaterNumberList,nthSmallest);
    }
    else{
        return pivot;
    }
}

Schöne Codierung, +1. Es ist jedoch kein zusätzlicher Platzbedarf erforderlich.
Hengameh

4

Ich habe das Finden des k-ten Minimums in n unsortierten Elementen mithilfe der dynamischen Programmierung, insbesondere der Turniermethode, implementiert. Die Ausführungszeit beträgt O (n + klog (n)). Der verwendete Mechanismus ist als eine der Methoden auf der Wikipedia-Seite zum Auswahlalgorithmus aufgeführt (wie in einem der obigen Beiträge angegeben). Sie können über den Algorithmus lesen und auch Code (Java) auf meiner Blog-Seite Finding Kth Minimum finden . Zusätzlich kann die Logik die Liste teilweise anordnen - geben Sie die ersten K min (oder max) in O (klog (n)) Zeit zurück.

Obwohl der Code das k-te Minimum liefert, kann eine ähnliche Logik verwendet werden, um das k-te Maximum in O (klog (n)) zu finden, wobei die Vorarbeiten zum Erstellen des Turnierbaums ignoriert werden.


3

Sie können dies in O (n + kn) = O (n) (für die Konstante k) für die Zeit und O (k) für den Raum tun, indem Sie die k größten Elemente verfolgen, die Sie gesehen haben.

Für jedes Element im Array können Sie die Liste der k größten Elemente scannen und das kleinste Element durch das neue ersetzen, wenn es größer ist.

Warrens vorrangige Heap-Lösung ist jedoch ordentlicher.


3
Dies hätte den schlimmsten Fall von O (n ^ 2), bei dem Sie nach dem kleinsten Element gefragt werden.
Elie

2
"Kleinster Gegenstand" bedeutet, dass k = n ist, also ist k nicht mehr konstant.
Tyler McHenry

Oder vielleicht behalten Sie einen Haufen (oder einen umgekehrten Haufen oder einen ausgeglichenen Baum) des größten k, den Sie bisher gesehen haben O(n log k)... entartet bei großen k immer noch zu O (nlogn). Ich würde denken, es würde gut funktionieren für kleine Werte von k jedoch ... möglicherweise schneller als einige der anderen hier erwähnten Algorithmen [???]
Rogerdpack

3

Sexy Schnellauswahl in Python

def quickselect(arr, k):
    '''
     k = 1 returns first element in ascending order.
     can be easily modified to return first element in descending order
    '''

    r = random.randrange(0, len(arr))

    a1 = [i for i in arr if i < arr[r]] '''partition'''
    a2 = [i for i in arr if i > arr[r]]

    if k <= len(a1):
        return quickselect(a1, k)
    elif k > len(arr)-len(a2):
        return quickselect(a2, k - (len(arr) - len(a2)))
    else:
        return arr[r]

Gute Lösung, außer dass dies das k-te kleinste Element in einer unsortierten Liste zurückgibt . Durch Umkehren der Vergleichsoperatoren im Listenverständnis a1 = [i for i in arr if i > arr[r]]und a2 = [i for i in arr if i < arr[r]]wird das k-te größte Element zurückgegeben.
Kaugummi

Von einem kleinen Benchmark aus ist es selbst auf großen Arrays schneller zu sortieren (mit numpy.sortfür numpy arrayoder sortedfür Listen) als mit dieser manuellen Implementierung.
Næreen

2

Suchen Sie den Median des Arrays in linearer Zeit und teilen Sie das Array mithilfe der Partitionsprozedur genau wie in Quicksort in zwei Teile, wobei die Werte links vom Median kleiner (<) als der Median und rechts größer als (>) sind Auch dies kann in kürzester Zeit geschehen. Gehen Sie nun zu dem Teil des Arrays, in dem das k-te Element liegt. Jetzt wird die Wiederholung zu: T (n) = T (n / 2) + cn, was mir O (n) ergibt.


Es ist nicht erforderlich, den Median zu finden. Ohne Median ist Ihr Ansatz immer noch in Ordnung.
Hengameh

2
Und wie finden Sie den Median in linearer Zeit, wage ich zu fragen? ... :)
Rogerdpack

2

Unten finden Sie den Link zur vollständigen Implementierung mit einer ausführlichen Erklärung, wie der Algorithmus zum Auffinden des K-ten Elements in einem unsortierten Algorithmus funktioniert. Die Grundidee besteht darin, das Array wie in QuickSort zu partitionieren. Um jedoch Extremfälle zu vermeiden (z. B. wenn das kleinste Element in jedem Schritt als Drehpunkt ausgewählt wird, so dass der Algorithmus in die Laufzeit O (n ^ 2) ausartet), wird eine spezielle Drehpunktauswahl angewendet, die als Median-of-Medians-Algorithmus bezeichnet wird. Die gesamte Lösung läuft im schlimmsten und im durchschnittlichen Fall in O (n) -Zeit.

Hier ist ein Link zum vollständigen Artikel (es geht darum, das kleinste K-te Element zu finden, aber das Prinzip ist das gleiche, um das größte K- te Element zu finden ):

Finden des k-ten kleinsten Elements in einem unsortierten Array


2

Gemäß diesem Artikel wird der folgende Algorithmus O(n)im schlimmsten Fall einige Zeit in Anspruch nehmen, um das k-te größte Element in einer Liste von n Elementen zu finden .

  1. Teilen Sie das Array in n / 5 Listen mit jeweils 5 Elementen.
  2. Finden Sie den Median in jedem Unterarray von 5 Elementen.
  3. Finden Sie rekursiv den Median aller Mediane, nennen wir ihn M.
  4. Partitionieren des Arrays in zwei Unterarrays Das 1. Unterarray enthält die Elemente, die größer als M sind. Nehmen wir an, dieses Unterarray ist a1, während das andere Unterarray die Elemente enthält, die kleiner als M sind. Nennen wir dieses Unterarray a2.
  5. Wenn k <= | a1 |, geben Sie die Auswahl (a1, k) zurück.
  6. Wenn k− 1 = | a1 |, gib M zurück.
  7. Wenn k> | a1 | + 1, Auswahl zurückgeben (a2, k −a1 - 1).

Analyse: Wie im Originalpapier vorgeschlagen:

Wir verwenden den Median, um die Liste in zwei Hälften zu unterteilen (die erste Hälfte, wenn k <= n/2, und die zweite Hälfte ansonsten). Dieser Algorithmus benötigt Zeit cnauf der ersten Rekursionsebene für eine Konstante c, cn/2auf der nächsten Ebene (da wir in einer Liste der Größe n / 2 rekursieren), cn/4auf der dritten Ebene und so weiter. Die Gesamtzeit beträgt cn + cn/2 + cn/4 + .... = 2cn = o(n).

Warum wird die Partitionsgröße 5 und nicht 3 angenommen?

Wie bereits erwähnt in Originalpapier :

Das Teilen der Liste durch 5 stellt eine Worst-Case-Aufteilung von 70 - 30 sicher. Mindestens die Hälfte der Mediane ist größer als der Median der Mediane, daher hat mindestens die Hälfte der n / 5 Blöcke mindestens 3 Elemente, und dies ergibt eine 3n/10Aufteilung, die ergibt bedeutet, dass die andere Partition im schlimmsten Fall 7n / 10 ist. Das heißt T(n) = T(n/5)+T(7n/10)+O(n). Since n/5+7n/10 < 1, die Worst-Case-Laufzeit ist O(n).

Jetzt habe ich versucht, den obigen Algorithmus wie folgt zu implementieren:

public static int findKthLargestUsingMedian(Integer[] array, int k) {
        // Step 1: Divide the list into n/5 lists of 5 element each.
        int noOfRequiredLists = (int) Math.ceil(array.length / 5.0);
        // Step 2: Find pivotal element aka median of medians.
        int medianOfMedian =  findMedianOfMedians(array, noOfRequiredLists);
        //Now we need two lists split using medianOfMedian as pivot. All elements in list listOne will be grater than medianOfMedian and listTwo will have elements lesser than medianOfMedian.
        List<Integer> listWithGreaterNumbers = new ArrayList<>(); // elements greater than medianOfMedian
        List<Integer> listWithSmallerNumbers = new ArrayList<>(); // elements less than medianOfMedian
        for (Integer element : array) {
            if (element < medianOfMedian) {
                listWithSmallerNumbers.add(element);
            } else if (element > medianOfMedian) {
                listWithGreaterNumbers.add(element);
            }
        }
        // Next step.
        if (k <= listWithGreaterNumbers.size()) return findKthLargestUsingMedian((Integer[]) listWithGreaterNumbers.toArray(new Integer[listWithGreaterNumbers.size()]), k);
        else if ((k - 1) == listWithGreaterNumbers.size()) return medianOfMedian;
        else if (k > (listWithGreaterNumbers.size() + 1)) return findKthLargestUsingMedian((Integer[]) listWithSmallerNumbers.toArray(new Integer[listWithSmallerNumbers.size()]), k-listWithGreaterNumbers.size()-1);
        return -1;
    }

    public static int findMedianOfMedians(Integer[] mainList, int noOfRequiredLists) {
        int[] medians = new int[noOfRequiredLists];
        for (int count = 0; count < noOfRequiredLists; count++) {
            int startOfPartialArray = 5 * count;
            int endOfPartialArray = startOfPartialArray + 5;
            Integer[] partialArray = Arrays.copyOfRange((Integer[]) mainList, startOfPartialArray, endOfPartialArray);
            // Step 2: Find median of each of these sublists.
            int medianIndex = partialArray.length/2;
            medians[count] = partialArray[medianIndex];
        }
        // Step 3: Find median of the medians.
        return medians[medians.length / 2];
    }

Nur zur Vervollständigung verwendet ein anderer Algorithmus die Prioritätswarteschlange und benötigt Zeit O(nlogn).

public static int findKthLargestUsingPriorityQueue(Integer[] nums, int k) {
        int p = 0;
        int numElements = nums.length;
        // create priority queue where all the elements of nums will be stored
        PriorityQueue<Integer> pq = new PriorityQueue<Integer>();

        // place all the elements of the array to this priority queue
        for (int n : nums) {
            pq.add(n);
        }

        // extract the kth largest element
        while (numElements - k + 1 > 0) {
            p = pq.poll();
            k++;
        }

        return p;
    }

Beide Algorithmen können wie folgt getestet werden:

public static void main(String[] args) throws IOException {
        Integer[] numbers = new Integer[]{2, 3, 5, 4, 1, 12, 11, 13, 16, 7, 8, 6, 10, 9, 17, 15, 19, 20, 18, 23, 21, 22, 25, 24, 14};
        System.out.println(findKthLargestUsingMedian(numbers, 8));
        System.out.println(findKthLargestUsingPriorityQueue(numbers, 8));
    }

Wie erwartet ist die Ausgabe: 18 18


@rogerdpack Ich habe den Link angegeben, dem ich gefolgt bin.
akhil_mittal

2

Wie wäre es mit diesem Ansatz?

Behalte a buffer of length kund a bei tmp_max, tmp_max zu bekommen ist O (k) und wird n-mal gemacht, also so etwas wieO(kn)

Geben Sie hier die Bildbeschreibung ein

Ist es richtig oder fehlt mir etwas?

Es übertrifft zwar nicht den durchschnittlichen Fall der Schnellauswahl und den schlimmsten Fall der Medianstatistikmethode, ist aber ziemlich einfach zu verstehen und zu implementieren.


1
Ich mag es, leichter zu verstehen. Obwohl die Komplexität O (nk) ist, wie Sie betont haben.
Hajjat

1

Durchlaufen Sie die Liste. Wenn der aktuelle Wert größer als der gespeicherte größte Wert ist, speichern Sie ihn als den größten Wert und drücken Sie die 1-4 nach unten, und 5 wird von der Liste gestrichen. Wenn nicht, vergleichen Sie es mit Nummer 2 und machen Sie dasselbe. Wiederholen Sie diesen Vorgang und vergleichen Sie ihn mit allen 5 gespeicherten Werten. dies sollte es in O (n) tun


Diese "Beule" ist O (n), wenn Sie ein Array verwenden, oder bis zu O (log n) (glaube ich), wenn Sie eine bessere Struktur verwenden.
Kirk Strauser

Es muss nicht O sein (log k) - wenn die Liste eine verknüpfte Liste ist, dann ist das Hinzufügen des neuen Elements oben und das Löschen des letzten Elements eher wie O (2)
Alnitak

Die Erhebung wäre O (k) für eine Array-gesicherte Liste, O (1) für eine entsprechend verknüpfte Liste. In beiden Fällen wird bei dieser Art von Frage im Allgemeinen davon ausgegangen, dass sie im Vergleich zu n nur eine minimale Auswirkung hat, und es werden keine weiteren Faktoren von n eingeführt.
Bobince

Es wäre auch O (1), wenn die Erhebung einen Ringpuffer verwendet
Alnitak

1
Wie auch immer, der Algorithmus des Kommentars ist unvollständig und berücksichtigt kein Element von n, das das neue (z. B.) zweitgrößte ist. Das Worst-Case-Verhalten, bei dem jedes Element in n mit jedem in der Highscore-Tabelle verglichen werden muss, ist O (kn) - aber das bedeutet wahrscheinlich immer noch O (n) in Bezug auf die Frage.
Bobince

1

Ich möchte eine Antwort vorschlagen

wenn wir die ersten k Elemente nehmen und sie in eine verknüpfte Liste von k Werten sortieren

Jetzt ist für jeden anderen Wert, selbst für den schlimmsten Fall, wenn wir eine Einfügungssortierung für Rest-nk-Werte durchführen, selbst im schlimmsten Fall die Anzahl der Vergleiche k * (nk) und für vorher zu sortierende k-Werte sei es k * (k-) 1) so kommt es heraus, dass (nk-k) ist, was o (n) ist

Prost


1
Das Sortieren dauert nicht lange ... der Algorithmus sollte in linearer Zeit ausgeführt werden
MrDatabase

1

Eine Erklärung des Median-of-Medians-Algorithmus zum Ermitteln der k-ten größten Ganzzahl aus n finden Sie hier: http://cs.indstate.edu/~spitla/presentation.pdf

Die Implementierung in c ++ ist unten:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int findMedian(vector<int> vec){
//    Find median of a vector
    int median;
    size_t size = vec.size();
    median = vec[(size/2)];
    return median;
}

int findMedianOfMedians(vector<vector<int> > values){
    vector<int> medians;

    for (int i = 0; i < values.size(); i++) {
        int m = findMedian(values[i]);
        medians.push_back(m);
    }

    return findMedian(medians);
}

void selectionByMedianOfMedians(const vector<int> values, int k){
//    Divide the list into n/5 lists of 5 elements each
    vector<vector<int> > vec2D;

    int count = 0;
    while (count != values.size()) {
        int countRow = 0;
        vector<int> row;

        while ((countRow < 5) && (count < values.size())) {
            row.push_back(values[count]);
            count++;
            countRow++;
        }
        vec2D.push_back(row);
    }

    cout<<endl<<endl<<"Printing 2D vector : "<<endl;
    for (int i = 0; i < vec2D.size(); i++) {
        for (int j = 0; j < vec2D[i].size(); j++) {
            cout<<vec2D[i][j]<<" ";
        }
        cout<<endl;
    }
    cout<<endl;

//    Calculating a new pivot for making splits
    int m = findMedianOfMedians(vec2D);
    cout<<"Median of medians is : "<<m<<endl;

//    Partition the list into unique elements larger than 'm' (call this sublist L1) and
//    those smaller them 'm' (call this sublist L2)
    vector<int> L1, L2;

    for (int i = 0; i < vec2D.size(); i++) {
        for (int j = 0; j < vec2D[i].size(); j++) {
            if (vec2D[i][j] > m) {
                L1.push_back(vec2D[i][j]);
            }else if (vec2D[i][j] < m){
                L2.push_back(vec2D[i][j]);
            }
        }
    }

//    Checking the splits as per the new pivot 'm'
    cout<<endl<<"Printing L1 : "<<endl;
    for (int i = 0; i < L1.size(); i++) {
        cout<<L1[i]<<" ";
    }

    cout<<endl<<endl<<"Printing L2 : "<<endl;
    for (int i = 0; i < L2.size(); i++) {
        cout<<L2[i]<<" ";
    }

//    Recursive calls
    if ((k - 1) == L1.size()) {
        cout<<endl<<endl<<"Answer :"<<m;
    }else if (k <= L1.size()) {
        return selectionByMedianOfMedians(L1, k);
    }else if (k > (L1.size() + 1)){
        return selectionByMedianOfMedians(L2, k-((int)L1.size())-1);
    }

}

int main()
{
    int values[] = {2, 3, 5, 4, 1, 12, 11, 13, 16, 7, 8, 6, 10, 9, 17, 15, 19, 20, 18, 23, 21, 22, 25, 24, 14};

    vector<int> vec(values, values + 25);

    cout<<"The given array is : "<<endl;
    for (int i = 0; i < vec.size(); i++) {
        cout<<vec[i]<<" ";
    }

    selectionByMedianOfMedians(vec, 8);

    return 0;
}

Diese Lösung funktioniert nicht. Sie müssen das Array sortieren, bevor Sie den Median für den Fall mit 5 Elementen zurückgeben.
Agnishom Chattopadhyay

1

Es gibt auch den Auswahlalgorithmus von Wirth , der einfacher zu implementieren ist als QuickSelect. Der Auswahlalgorithmus von Wirth ist langsamer als der von QuickSelect, wird jedoch mit einigen Verbesserungen schneller.

Genauer. Unter Verwendung der MODIFIND-Optimierung von Vladimir Zabrodsky und der Auswahl des Median-of-3-Pivots und unter Berücksichtigung der letzten Schritte des Partitionierungsteils des Algorithmus habe ich den folgenden Algorithmus entwickelt (möglicherweise "LefSelect" genannt):

#define F_SWAP(a,b) { float temp=(a);(a)=(b);(b)=temp; }

# Note: The code needs more than 2 elements to work
float lefselect(float a[], const int n, const int k) {
    int l=0, m = n-1, i=l, j=m;
    float x;

    while (l<m) {
        if( a[k] < a[i] ) F_SWAP(a[i],a[k]);
        if( a[j] < a[i] ) F_SWAP(a[i],a[j]);
        if( a[j] < a[k] ) F_SWAP(a[k],a[j]);

        x=a[k];
        while (j>k & i<k) {
            do i++; while (a[i]<x);
            do j--; while (a[j]>x);

            F_SWAP(a[i],a[j]);
        }
        i++; j--;

        if (j<k) {
            while (a[i]<x) i++;
            l=i; j=m;
        }
        if (k<i) {
            while (x<a[j]) j--;
            m=j; i=l;
        }
    }
    return a[k];
}

In Benchmarks, die ich hier durchgeführt habe , ist LefSelect 20-30% schneller als QuickSelect.


1

Haskell-Lösung:

kthElem index list = sort list !! index

withShape ~[]     []     = []
withShape ~(x:xs) (y:ys) = x : withShape xs ys

sort []     = []
sort (x:xs) = (sort ls `withShape` ls) ++ [x] ++ (sort rs `withShape` rs)
  where
   ls = filter (<  x)
   rs = filter (>= x)

Dadurch wird der Median der Medianlösungen mithilfe der withShape-Methode implementiert, um die Größe einer Partition zu ermitteln, ohne sie tatsächlich zu berechnen.


1

Hier ist eine C ++ - Implementierung von Randomized QuickSelect. Die Idee ist, zufällig ein Pivot-Element auszuwählen. Um eine zufällige Partition zu implementieren, verwenden wir eine Zufallsfunktion, rand (), um einen Index zwischen l und r zu generieren, tauschen das Element bei einem zufällig generierten Index mit dem letzten Element aus und rufen schließlich den Standardpartitionsprozess auf, der das letzte Element als Pivot verwendet.

#include<iostream>
#include<climits>
#include<cstdlib>
using namespace std;

int randomPartition(int arr[], int l, int r);

// This function returns k'th smallest element in arr[l..r] using
// QuickSort based method.  ASSUMPTION: ALL ELEMENTS IN ARR[] ARE DISTINCT
int kthSmallest(int arr[], int l, int r, int k)
{
    // If k is smaller than number of elements in array
    if (k > 0 && k <= r - l + 1)
    {
        // Partition the array around a random element and
        // get position of pivot element in sorted array
        int pos = randomPartition(arr, l, r);

        // If position is same as k
        if (pos-l == k-1)
            return arr[pos];
        if (pos-l > k-1)  // If position is more, recur for left subarray
            return kthSmallest(arr, l, pos-1, k);

        // Else recur for right subarray
        return kthSmallest(arr, pos+1, r, k-pos+l-1);
    }

    // If k is more than number of elements in array
    return INT_MAX;
}

void swap(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

// Standard partition process of QuickSort().  It considers the last
// element as pivot and moves all smaller element to left of it and
// greater elements to right. This function is used by randomPartition()
int partition(int arr[], int l, int r)
{
    int x = arr[r], i = l;
    for (int j = l; j <= r - 1; j++)
    {
        if (arr[j] <= x) //arr[i] is bigger than arr[j] so swap them
        {
            swap(&arr[i], &arr[j]);
            i++;
        }
    }
    swap(&arr[i], &arr[r]); // swap the pivot
    return i;
}

// Picks a random pivot element between l and r and partitions
// arr[l..r] around the randomly picked element using partition()
int randomPartition(int arr[], int l, int r)
{
    int n = r-l+1;
    int pivot = rand() % n;
    swap(&arr[l + pivot], &arr[r]);
    return partition(arr, l, r);
}

// Driver program to test above methods
int main()
{
    int arr[] = {12, 3, 5, 7, 4, 19, 26};
    int n = sizeof(arr)/sizeof(arr[0]), k = 3;
    cout << "K'th smallest element is " << kthSmallest(arr, 0, n-1, k);
    return 0;
}

Die Zeitkomplexität der obigen Lösung im ungünstigsten Fall ist immer noch O (n2). Im schlimmsten Fall kann die zufällige Funktion immer ein Eckelement auswählen. Die erwartete zeitliche Komplexität von oben randomisiertem QuickSelect beträgt Θ (n)


Schöne Codierung. Vielen Dank für das Teilen, +1
Hengameh

1
  1. Prioritätswarteschlange erstellen lassen.
  2. Fügen Sie alle Elemente in den Heap ein.
  3. Rufen Sie poll () k mal auf.

    public static int getKthLargestElements(int[] arr)
    {
        PriorityQueue<Integer> pq =  new PriorityQueue<>((x , y) -> (y-x));
        //insert all the elements into heap
        for(int ele : arr)
           pq.offer(ele);
        // call poll() k times
        int i=0;
        while(i&lt;k)
         {
           int result = pq.poll();
         } 
       return result;        
    }
    

0

Dies ist eine Implementierung in Javascript.

Wenn Sie die Einschränkung aufheben, dass Sie das Array nicht ändern können, können Sie die Verwendung von zusätzlichem Speicher verhindern, indem Sie zwei Indizes verwenden, um die "aktuelle Partition" zu identifizieren (im klassischen Quicksort-Stil - http://www.nczonline.net/blog/2012/). 11/27 / Informatik-in-Javascript-Quicksort / ).

function kthMax(a, k){
    var size = a.length;

    var pivot = a[ parseInt(Math.random()*size) ]; //Another choice could have been (size / 2) 

    //Create an array with all element lower than the pivot and an array with all element higher than the pivot
    var i, lowerArray = [], upperArray = [];
    for (i = 0; i  < size; i++){
        var current = a[i];

        if (current < pivot) {
            lowerArray.push(current);
        } else if (current > pivot) {
            upperArray.push(current);
        }
    }

    //Which one should I continue with?
    if(k <= upperArray.length) {
        //Upper
        return kthMax(upperArray, k);
    } else {
        var newK = k - (size - lowerArray.length);

        if (newK > 0) {
            ///Lower
            return kthMax(lowerArray, newK);
        } else {
            //None ... it's the current pivot!
            return pivot;
        }   
    }
}  

Wenn Sie die Leistung testen möchten, können Sie diese Variante verwenden:

    function kthMax (a, k, logging) {
         var comparisonCount = 0; //Number of comparison that the algorithm uses
         var memoryCount = 0;     //Number of integers in memory that the algorithm uses
         var _log = logging;

         if(k < 0 || k >= a.length) {
            if (_log) console.log ("k is out of range"); 
            return false;
         }      

         function _kthmax(a, k){
             var size = a.length;
             var pivot = a[parseInt(Math.random()*size)];
             if(_log) console.log("Inputs:", a,  "size="+size, "k="+k, "pivot="+pivot);

             // This should never happen. Just a nice check in this exercise
             // if you are playing with the code to avoid never ending recursion            
             if(typeof pivot === "undefined") {
                 if (_log) console.log ("Ops..."); 
                 return false;
             }

             var i, lowerArray = [], upperArray = [];
             for (i = 0; i  < size; i++){
                 var current = a[i];
                 if (current < pivot) {
                     comparisonCount += 1;
                     memoryCount++;
                     lowerArray.push(current);
                 } else if (current > pivot) {
                     comparisonCount += 2;
                     memoryCount++;
                     upperArray.push(current);
                 }
             }
             if(_log) console.log("Pivoting:",lowerArray, "*"+pivot+"*", upperArray);

             if(k <= upperArray.length) {
                 comparisonCount += 1;
                 return _kthmax(upperArray, k);
             } else if (k > size - lowerArray.length) {
                 comparisonCount += 2;
                 return _kthmax(lowerArray, k - (size - lowerArray.length));
             } else {
                 comparisonCount += 2;
                 return pivot;
             }
     /* 
      * BTW, this is the logic for kthMin if we want to implement that... ;-)
      * 

             if(k <= lowerArray.length) {
                 return kthMin(lowerArray, k);
             } else if (k > size - upperArray.length) {
                 return kthMin(upperArray, k - (size - upperArray.length));
             } else 
                 return pivot;
     */            
         }

         var result = _kthmax(a, k);
         return {result: result, iterations: comparisonCount, memory: memoryCount};
     }

Der Rest des Codes dient nur dazu, einen Spielplatz zu erstellen:

    function getRandomArray (n){
        var ar = [];
        for (var i = 0, l = n; i < l; i++) {
            ar.push(Math.round(Math.random() * l))
        }

        return ar;
    }

    //Create a random array of 50 numbers
    var ar = getRandomArray (50);   

Führen Sie nun einige Male Tests durch. Aufgrund von Math.random () werden jedes Mal unterschiedliche Ergebnisse erzielt:

    kthMax(ar, 2, true);
    kthMax(ar, 2);
    kthMax(ar, 2);
    kthMax(ar, 2);
    kthMax(ar, 2);
    kthMax(ar, 2);
    kthMax(ar, 34, true);
    kthMax(ar, 34);
    kthMax(ar, 34);
    kthMax(ar, 34);
    kthMax(ar, 34);
    kthMax(ar, 34);

Wenn Sie es einige Male testen, können Sie sogar empirisch sehen, dass die Anzahl der Iterationen im Durchschnitt O (n) ~ = Konstante * n ist und der Wert von k den Algorithmus nicht beeinflusst.


0

Ich habe mir diesen Algorithmus ausgedacht und scheint O (n) zu sein:

Nehmen wir an, k = 3 und wir möchten das drittgrößte Element im Array finden. Ich würde drei Variablen erstellen und jedes Element des Arrays mit dem Minimum dieser drei Variablen vergleichen. Wenn das Array-Element größer als unser Minimum ist, ersetzen wir die Variable min durch den Elementwert. Wir machen dasselbe bis zum Ende des Arrays. Das Minimum unserer drei Variablen ist das drittgrößte Element im Array.

define variables a=0, b=0, c=0
iterate through the array items
    find minimum a,b,c
    if item > min then replace the min variable with item value
    continue until end of array
the minimum of a,b,c is our answer

Und um den K-ten größten Gegenstand zu finden, benötigen wir K-Variablen.

Beispiel: (k = 3)

[1,2,4,1,7,3,9,5,6,2,9,8]

Final variable values:

a=7 (answer)
b=8
c=9

Kann jemand dies bitte überprüfen und mich wissen lassen, was mir fehlt?


0

Hier ist die Implementierung des vorgeschlagenen Algorithmus eladv (ich habe hier auch die Implementierung mit zufälligem Pivot angegeben):

public class Median {

    public static void main(String[] s) {

        int[] test = {4,18,20,3,7,13,5,8,2,1,15,17,25,30,16};
        System.out.println(selectK(test,8));

        /*
        int n = 100000000;
        int[] test = new int[n];
        for(int i=0; i<test.length; i++)
            test[i] = (int)(Math.random()*test.length);

        long start = System.currentTimeMillis();
        random_selectK(test, test.length/2);
        long end = System.currentTimeMillis();
        System.out.println(end - start);
        */
    }

    public static int random_selectK(int[] a, int k) {
        if(a.length <= 1)
            return a[0];

        int r = (int)(Math.random() * a.length);
        int p = a[r];

        int small = 0, equal = 0, big = 0;
        for(int i=0; i<a.length; i++) {
            if(a[i] < p) small++;
            else if(a[i] == p) equal++;
            else if(a[i] > p) big++;
        }

        if(k <= small) {
            int[] temp = new int[small];
            for(int i=0, j=0; i<a.length; i++)
                if(a[i] < p)
                    temp[j++] = a[i];
            return random_selectK(temp, k);
        }

        else if (k <= small+equal)
            return p;

        else {
            int[] temp = new int[big];
            for(int i=0, j=0; i<a.length; i++)
                if(a[i] > p)
                    temp[j++] = a[i];
            return random_selectK(temp,k-small-equal);
        }
    }

    public static int selectK(int[] a, int k) {
        if(a.length <= 5) {
            Arrays.sort(a);
            return a[k-1];
        }

        int p = median_of_medians(a);

        int small = 0, equal = 0, big = 0;
        for(int i=0; i<a.length; i++) {
            if(a[i] < p) small++;
            else if(a[i] == p) equal++;
            else if(a[i] > p) big++;
        }

        if(k <= small) {
            int[] temp = new int[small];
            for(int i=0, j=0; i<a.length; i++)
                if(a[i] < p)
                    temp[j++] = a[i];
            return selectK(temp, k);
        }

        else if (k <= small+equal)
            return p;

        else {
            int[] temp = new int[big];
            for(int i=0, j=0; i<a.length; i++)
                if(a[i] > p)
                    temp[j++] = a[i];
            return selectK(temp,k-small-equal);
        }
    }

    private static int median_of_medians(int[] a) {
        int[] b = new int[a.length/5];
        int[] temp = new int[5];
        for(int i=0; i<b.length; i++) {
            for(int j=0; j<5; j++)
                temp[j] = a[5*i + j];
            Arrays.sort(temp);
            b[i] = temp[2];
        }

        return selectK(b, b.length/2 + 1);
    }
}

0

Es ähnelt der quickSort-Strategie, bei der wir einen beliebigen Drehpunkt auswählen und die kleineren Elemente nach links und die größeren nach rechts bringen

    public static int kthElInUnsortedList(List<int> list, int k)
    {
        if (list.Count == 1)
            return list[0];

        List<int> left = new List<int>();
        List<int> right = new List<int>();

        int pivotIndex = list.Count / 2;
        int pivot = list[pivotIndex]; //arbitrary

        for (int i = 0; i < list.Count && i != pivotIndex; i++)
        {
            int currentEl = list[i];
            if (currentEl < pivot)
                left.Add(currentEl);
            else
                right.Add(currentEl);
        }

        if (k == left.Count + 1)
            return pivot;

        if (left.Count < k)
            return kthElInUnsortedList(right, k - left.Count - 1);
        else
            return kthElInUnsortedList(left, k);
    }


0

Sie finden das k-te kleinste Element in O (n) Zeit und konstantem Raum. Wenn wir berücksichtigen, ist das Array nur für ganze Zahlen.

Der Ansatz besteht darin, eine binäre Suche im Bereich der Array-Werte durchzuführen. Wenn wir einen min_value und einen max_value beide im ganzzahligen Bereich haben, können wir eine binäre Suche in diesem Bereich durchführen. Wir können eine Komparatorfunktion schreiben, die uns sagt, ob ein Wert der k-kleinste oder kleiner als der k-kleinste oder größer als der k-kleinste ist. Führen Sie die binäre Suche durch, bis Sie die k-kleinste Zahl erreichen

Hier ist der Code dafür

Klasse Lösung:

def _iskthsmallest(self, A, val, k):
    less_count, equal_count = 0, 0
    for i in range(len(A)):
        if A[i] == val: equal_count += 1
        if A[i] < val: less_count += 1

    if less_count >= k: return 1
    if less_count + equal_count < k: return -1
    return 0

def kthsmallest_binary(self, A, min_val, max_val, k):
    if min_val == max_val:
        return min_val
    mid = (min_val + max_val)/2
    iskthsmallest = self._iskthsmallest(A, mid, k)
    if iskthsmallest == 0: return mid
    if iskthsmallest > 0: return self.kthsmallest_binary(A, min_val, mid, k)
    return self.kthsmallest_binary(A, mid+1, max_val, k)

# @param A : tuple of integers
# @param B : integer
# @return an integer
def kthsmallest(self, A, k):
    if not A: return 0
    if k > len(A): return 0
    min_val, max_val = min(A), max(A)
    return self.kthsmallest_binary(A, min_val, max_val, k)

0

Es gibt auch einen Algorithmus, der den Schnellauswahlalgorithmus übertrifft. Es heißt Floyd-Rivets (FR) -Algorithmus .

Originalartikel: https://doi.org/10.1145/360680.360694

Herunterladbare Version: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.309.7108&rep=rep1&type=pdf

Wikipedia-Artikel https://en.wikipedia.org/wiki/Floyd%E2%80%93Rivest_algorithm

Ich habe versucht, Quickselect und FR-Algorithmus in C ++ zu implementieren. Außerdem habe ich sie mit den Standardimplementierungen der C ++ - Bibliothek std :: nth_element verglichen (was im Grunde eine Introselect-Mischung aus Quickselect und Heapselect ist). Das Ergebnis war die Schnellauswahl und nth_element lief im Durchschnitt vergleichbar, aber der FR-Algorithmus lief ca. doppelt so schnell im Vergleich zu ihnen.

Beispielcode, den ich für den FR-Algorithmus verwendet habe:

template <typename T>
T FRselect(std::vector<T>& data, const size_t& n)
{
    if (n == 0)
        return *(std::min_element(data.begin(), data.end()));
    else if (n == data.size() - 1)
        return *(std::max_element(data.begin(), data.end()));
    else
        return _FRselect(data, 0, data.size() - 1, n);
}

template <typename T>
T _FRselect(std::vector<T>& data, const size_t& left, const size_t& right, const size_t& n)
{
    size_t leftIdx = left;
    size_t rightIdx = right;

    while (rightIdx > leftIdx)
    {
        if (rightIdx - leftIdx > 600)
        {
            size_t range = rightIdx - leftIdx + 1;
            long long i = n - (long long)leftIdx + 1;
            long long z = log(range);
            long long s = 0.5 * exp(2 * z / 3);
            long long sd = 0.5 * sqrt(z * s * (range - s) / range) * sgn(i - (long long)range / 2);

            size_t newLeft = fmax(leftIdx, n - i * s / range + sd);
            size_t newRight = fmin(rightIdx, n + (range - i) * s / range + sd);

            _FRselect(data, newLeft, newRight, n);
        }
        T t = data[n];
        size_t i = leftIdx;
        size_t j = rightIdx;
        // arrange pivot and right index
        std::swap(data[leftIdx], data[n]);
        if (data[rightIdx] > t)
            std::swap(data[rightIdx], data[leftIdx]);

        while (i < j)
        {
            std::swap(data[i], data[j]);
            ++i; --j;
            while (data[i] < t) ++i;
            while (data[j] > t) --j;
        }

        if (data[leftIdx] == t)
            std::swap(data[leftIdx], data[j]);
        else
        {
            ++j;
            std::swap(data[j], data[rightIdx]);
        }
        // adjust left and right towards the boundaries of the subset
        // containing the (k - left + 1)th smallest element
        if (j <= n)
            leftIdx = j + 1;
        if (n <= j)
            rightIdx = j - 1;
    }

    return data[leftIdx];
}

template <typename T>
int sgn(T val) {
    return (T(0) < val) - (val < T(0));
}

-1

Was ich tun würde, ist Folgendes:

initialize empty doubly linked list l
for each element e in array
    if e larger than head(l)
        make e the new head of l
        if size(l) > k
            remove last element from l

the last element of l should now be the kth largest element

Sie können einfach Zeiger auf das erste und letzte Element in der verknüpften Liste speichern. Sie ändern sich nur, wenn Aktualisierungen an der Liste vorgenommen werden.

Aktualisieren:

initialize empty sorted tree l
for each element e in array
    if e between head(l) and tail(l)
        insert e into l // O(log k)
        if size(l) > k
            remove last element from l

the last element of l should now be the kth largest element

Was ist, wenn e kleiner als Kopf (l) ist? Es könnte immer noch größer als das k-te größte Element sein, würde aber niemals zu dieser Liste hinzugefügt werden. Sie müssen die Liste der Elemente in aufsteigender Reihenfolge sortieren, damit dies funktioniert.
Elie

Sie haben Recht, ich denke, ich muss noch etwas darüber nachdenken. :-)
Jasper Bekkers

Die Lösung wäre, zu überprüfen, ob sich e zwischen Kopf (l) und Schwanz (l) befindet, und es an der richtigen Position einzuführen, wenn dies der Fall ist. Machen Sie dieses O (kn). Sie können es auf O (n log k) setzen, wenn Sie einen Binärbaum verwenden, der die Min- und Max-Elemente verfolgt.
Jasper Bekkers

-1

Zuerst können wir eine BST aus einem unsortierten Array erstellen, das O (n) Zeit benötigt, und aus der BST können wir das k-te kleinste Element in O (log (n)) finden, das insgesamt bis zu einer Ordnung von O (n) zählt.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.