Ich habe ein sortiertes JavaScript-Array und möchte ein weiteres Element in das Array einfügen, sodass das resultierende Array sortiert bleibt. Ich könnte sicherlich eine einfache Einfügefunktion im QuickSort-Stil implementieren:
var array = [1,2,3,4,5,6,7,8,9];
var element = 3.5;
function insert(element, array) {
array.splice(locationOf(element, array) + 1, 0, element);
return array;
}
function locationOf(element, array, start, end) {
start = start || 0;
end = end || array.length;
var pivot = parseInt(start + (end - start) / 2, 10);
if (end-start <= 1 || array[pivot] === element) return pivot;
if (array[pivot] < element) {
return locationOf(element, array, pivot, end);
} else {
return locationOf(element, array, start, pivot);
}
}
console.log(insert(element, array));
[WARNUNG] Dieser Code hat einen Fehler beim Versuch, an den Anfang des Arrays einzufügen, z. B. insert(2, [3, 7 ,9]
) erzeugt falsche [3, 2, 7, 9].
Ich habe jedoch festgestellt, dass Implementierungen der Array.sort-Funktion dies möglicherweise für mich tun können, und zwar nativ:
var array = [1,2,3,4,5,6,7,8,9];
var element = 3.5;
function insert(element, array) {
array.push(element);
array.sort(function(a, b) {
return a - b;
});
return array;
}
console.log(insert(element, array));
Gibt es einen guten Grund, die erste Implementierung der zweiten vorzuziehen?
Bearbeiten : Beachten Sie, dass im allgemeinen Fall eine O-Einfügung (log (n)) (wie im ersten Beispiel implementiert) schneller ist als ein generischer Sortieralgorithmus. Dies ist jedoch insbesondere bei JavaScript nicht unbedingt der Fall. Beachten Sie, dass:
- Der beste Fall für mehrere Einfügealgorithmen ist O (n), das sich immer noch erheblich von O (log (n)) unterscheidet, aber nicht ganz so schlecht wie O (n log (n)) ist, wie unten erwähnt. Dies hängt vom jeweiligen verwendeten Sortieralgorithmus ab (siehe Implementierung von Javascript Array.sort? ).
- Die Sortiermethode in JavaScript ist eine native Funktion, sodass potenziell große Vorteile erzielt werden können - O (log (n)) mit einem großen Koeffizienten kann für Datensätze mit angemessener Größe immer noch viel schlechter sein als O (n).
quelle
splice()
(z. B. Ihr 1. Beispiel), ist bereits O (n). Selbst wenn intern keine neue Kopie des gesamten Arrays erstellt wird, müssen möglicherweise alle n Elemente um 1 Position zurückgeschoben werden, wenn das Element an Position 0 eingefügt werden soll. Vielleicht ist es schnell, weil es eine native Funktion ist und die Konstante ist niedrig, aber es ist trotzdem O (n).parseInt
Verwenden SieMath.floor
stattdessen nicht use .Math.floor
ist viel schneller alsparseInt
: jsperf.com/test-parseint-and-math-floorAntworten:
Nur als einzelner Datenpunkt habe ich dies für Kicks getestet, indem ich 1000 zufällige Elemente in ein Array von 100.000 vorsortierten Zahlen mit den beiden Methoden unter Verwendung von Chrome unter Windows 7 eingefügt habe:
Zumindest bei diesem Setup macht die native Methode dies nicht wett. Dies gilt auch für kleine Datenmengen, bei denen 100 Elemente in ein Array von 1000 eingefügt werden:
quelle
Array.prototype.sort
, verlieren Sie die Vorteile von C ++, weil die JS-Funktion so oft aufgerufen wird.Einfach ( Demo ):
quelle
x >>> 1
ist eine binäre Rechtsverschiebung um 1 Position, was effektiv nur eine Division durch 2 ist. ZB für 11:1011
->101
Ergebnisse zu 5.>>> 1
und ( gesehen hier und dort )>> 1
?>>>
ist eine vorzeichenlose Rechtsverschiebung, während sie>>
das Vorzeichen erweitert - alles läuft auf die speicherinterne Darstellung negativer Zahlen hinaus, wobei das hohe Bit gesetzt wird, wenn es negativ ist. Wenn Sie also mit0b1000
1 nach rechts verschieben,>>
erhalten Sie0b1100
, wenn Sie stattdessen verwenden, erhalten>>>
Sie0b0100
. Während es in dem in der Antwort angegebenen Fall nicht wirklich wichtig ist (die Zahl, die verschoben wird, ist weder größer als der Maximalwert einer vorzeichenbehafteten positiven 32-Bit-Ganzzahl noch negativ), ist es wichtig, in diesen beiden Fällen die richtige zu verwenden (Sie müssen auswählen, welchen Fall Sie behandeln müssen).0b1000
1 Stelle nach rechts verschieben,>>
erhalten Sie0b1100
". Nein, du verstehst0b0100
. Das Ergebnis der verschiedenen Rechtsverschiebungsoperatoren ist für alle Werte gleich, mit Ausnahme von negativen Zahlen und Zahlen größer als 2 ^ 31 (dh Zahlen mit einer 1 im ersten Bit).Sehr gute und bemerkenswerte Frage mit einer sehr interessanten Diskussion! Ich habe die
Array.sort()
Funktion auch verwendet, nachdem ich ein einzelnes Element in einem Array mit einigen Tausend Objekten verschoben hatte.Ich musste Ihre
locationOf
Funktion für meinen Zweck erweitern, weil ich komplexe Objekte habe und daher eine Vergleichsfunktion wie inArray.sort()
:quelle
return c == -1 ? pivot : pivot + 1;
, um den richtigen Index zurückzugeben. Andernfalls würde die Funktion für ein Array mit der Länge 1 -1 oder 0 zurückgeben.>> 1
sollte schneller (oder nicht langsamer) sein als/ 2
comparer
Funktion erkennen. In diesem Algorithmus ist es im Vergleich zu ,+-1
aber es könnte willkürlicher Wert sein<0
/>0
. Siehe Vergleichsfunktion . Der problematische Teil ist nicht nur dieswitch
Aussage, sondern auch die Linie:if (end - start <= 1) return c == -1 ? pivot - 1 : pivot;
woc
wird auch verglichen-1
.Ihr Code enthält einen Fehler. Es sollte lauten:
Ohne dieses Update kann der Code niemals ein Element am Anfang des Arrays einfügen.
quelle
Ich weiß, dass dies eine alte Frage ist, die bereits eine Antwort hat, und es gibt eine Reihe anderer anständiger Antworten. Ich sehe einige Antworten, die vorschlagen, dass Sie dieses Problem lösen können, indem Sie den korrekten Einfügeindex in O (log n) nachschlagen - Sie können, aber Sie können in dieser Zeit nicht einfügen, da das Array teilweise kopiert werden muss, um es zu erstellen Platz.
Fazit: Wenn Sie wirklich O (log n) Einfügungen und Löschungen in ein sortiertes Array benötigen, benötigen Sie eine andere Datenstruktur - kein Array. Sie sollten einen B-Baum verwenden . Die Leistungssteigerungen, die Sie durch die Verwendung eines B-Tree für einen großen Datensatz erzielen, werden die hier angebotenen Verbesserungen in den Schatten stellen.
Wenn Sie ein Array verwenden müssen. Ich biete den folgenden Code basierend auf der Einfügesortierung an, der genau dann funktioniert, wenn das Array bereits sortiert ist. Dies ist nützlich für den Fall, dass Sie nach jedem Einsatz neu greifen müssen:
Es sollte in O (n) arbeiten, was meiner Meinung nach das Beste ist, was Sie tun können. Wäre schöner, wenn js Mehrfachzuweisungen unterstützen würde. Hier ist ein Beispiel zum Spielen:
Aktualisieren:
das könnte schneller sein:
JS Bin Link aktualisiert
quelle
Ihre Einfügefunktion setzt voraus, dass das angegebene Array sortiert ist. Sie sucht direkt nach dem Ort, an dem das neue Element eingefügt werden kann, normalerweise indem Sie nur einige der Elemente im Array betrachten.
Die allgemeine Sortierfunktion eines Arrays kann diese Verknüpfungen nicht verwenden. Offensichtlich müssen zumindest alle Elemente im Array überprüft werden, um festzustellen, ob sie bereits korrekt angeordnet sind. Diese Tatsache allein macht die allgemeine Sortierung langsamer als die Einfügefunktion.
Ein generischer Sortieralgorithmus ist normalerweise im Durchschnitt O (n ⋅ log (n)) und abhängig von der Implementierung kann es tatsächlich der schlimmste Fall sein, wenn das Array bereits sortiert ist, was zu Komplexitäten von O (n 2 ) führt . Die direkte Suche nach der Einfügeposition hat stattdessen nur eine Komplexität von O (log (n)) , sodass sie immer viel schneller ist.
quelle
Für eine kleine Anzahl von Artikeln ist der Unterschied ziemlich trivial. Wenn Sie jedoch viele Elemente einfügen oder mit einem sehr großen Array arbeiten, verursacht das Aufrufen von .sort () nach jedem Einfügen einen enormen Overhead.
Am Ende habe ich genau für diesen Zweck eine ziemlich raffinierte binäre Such- / Einfügefunktion geschrieben, also dachte ich, ich würde sie teilen. Da
while
anstelle der Rekursion eine Schleife verwendet wird, werden zusätzliche Funktionsaufrufe nicht belauscht, sodass die Leistung meiner Meinung nach sogar noch besser ist als bei den ursprünglich veröffentlichten Methoden. StandardmäßigArray.sort()
wird der Standardkomparator emuliert , auf Wunsch wird jedoch eine benutzerdefinierte Komparatorfunktion akzeptiert.Wenn Sie andere Bibliotheken verwenden möchten, bietet lodash die Funktionen sortiertIndex und sortiertLastIndex , die anstelle der
while
Schleife verwendet werden können. Die beiden möglichen Nachteile sind: 1) Die Leistung ist nicht so gut wie bei meiner Methode (ich bin mir nicht sicher, wie viel schlechter sie ist) und 2) Sie akzeptiert keine benutzerdefinierte Komparatorfunktion, sondern nur eine Methode, um den zu vergleichenden Wert zu erhalten (Ich nehme an, mit dem Standardkomparator).quelle
arr.splice()
ist sicherlich O (n) Zeitkomplexität.Hier einige Gedanken: Wenn Sie sich wirklich Gedanken über die Laufzeit Ihres Codes machen, sollten Sie zunächst wissen, was passiert, wenn Sie die integrierten Funktionen aufrufen! Ich weiß nicht von oben nach unten in Javascript, aber ein kurzer Blick auf die Spleißfunktion hat dies zurückgegeben , was darauf hindeutet, dass Sie bei jedem Aufruf ein ganz neues Array erstellen! Ich weiß nicht, ob es wirklich darauf ankommt, aber es hängt sicherlich mit der Effizienz zusammen. Ich sehe, dass Breton in den Kommentaren bereits darauf hingewiesen hat, aber es gilt sicherlich für jede Array-Manipulationsfunktion, die Sie wählen.
Wie auch immer, um das Problem tatsächlich zu lösen.
Als ich las, dass Sie sortieren wollten, war mein erster Gedanke, die Einfügesortierung zu verwenden ! . Dies ist praktisch, da es in linearer Zeit auf sortierten oder nahezu sortierten Listen ausgeführt wird . Da Ihre Arrays nur 1 Element außerhalb der Reihenfolge haben, gilt dies als nahezu sortiert (mit Ausnahme von Arrays der Größe 2 oder 3 oder was auch immer, aber zu diesem Zeitpunkt, komm schon). Nun, die Implementierung der Sortierung ist nicht schlecht, aber es ist ein Ärger, mit dem Sie sich vielleicht nicht befassen möchten, und auch hier weiß ich nichts über Javascript und ob es einfach oder schwer sein wird oder so. Dadurch entfällt die Notwendigkeit Ihrer Suchfunktion, und Sie drücken einfach (wie von Breton vorgeschlagen).
Zweitens scheint Ihre "Quicksort-ähnliche" Suchfunktion ein binärer Suchalgorithmus zu sein! Es ist ein sehr schöner Algorithmus, intuitiv und schnell, aber mit einem Haken: Es ist bekanntermaßen schwierig, ihn richtig zu implementieren. Ich werde es nicht wagen zu sagen, ob deins korrekt ist oder nicht (ich hoffe es ist natürlich! :)), aber sei vorsichtig, wenn du es benutzen willst.
Zusammenfassung: Die Verwendung von "push" mit Einfügesortierung funktioniert in linearer Zeit (vorausgesetzt, der Rest des Arrays ist sortiert) und vermeidet unordentliche Anforderungen an den binären Suchalgorithmus. Ich weiß nicht, ob dies der beste Weg ist (die zugrunde liegende Implementierung von Arrays, vielleicht macht es eine verrückte eingebaute Funktion besser, wer weiß), aber es scheint mir vernünftig. :) - Agor.
quelle
splice()
bereits O (n) ist. Selbst wenn intern keine neue Kopie des gesamten Arrays erstellt wird, müssen möglicherweise alle n Elemente um 1 Position zurückgeschoben werden, wenn das Element an Position 0 eingefügt werden soll.Hier ist ein Vergleich von vier verschiedenen Algorithmen, um dies zu erreichen: https://jsperf.com/sorted-array-insert-comparison/1
Algorithmen
Naiv ist immer schrecklich. Es scheint für kleine Array-Größen, die anderen drei unterscheiden sich nicht zu sehr, aber für größere Arrays übertreffen die letzten 2 den einfachen linearen Ansatz.
quelle
Hier ist eine Version, die lodash verwendet.
Hinweis: sortedIndex führt eine binäre Suche durch.
quelle
Die beste Datenstruktur, die ich mir vorstellen kann, ist eine indizierte Sprungliste, die die Einfügeeigenschaften verknüpfter Listen mit einer Hierarchiestruktur beibehält, die Protokollzeitoperationen ermöglicht. Im Durchschnitt können Such-, Einfüge- und Direktzugriffssuchen in O (log n) -Zeit durchgeführt werden.
Ein Auftragsstatistikbaum ermöglicht die Indizierung der Protokollzeit mit einer Rangfunktion.
Wenn Sie keinen Direktzugriff benötigen, aber O (log n) einfügen und nach Schlüsseln suchen müssen, können Sie die Array-Struktur verlassen und jede Art von binärem Suchbaum verwenden .
Keine der Antworten, die verwendet
array.splice()
werden, ist überhaupt effizient, da dies durchschnittlich O (n) Zeit ist. Was ist die zeitliche Komplexität von array.splice () in Google Chrome?quelle
Is there a good reason to choose [splice into location found] over [push & sort]?
Hier ist meine Funktion, verwendet die binäre Suche, um einen Artikel zu finden und fügt ihn dann entsprechend ein:
quelle
Sortieren Sie nicht nach jedem Artikel neu, es ist übertrieben.
Wenn nur ein Element eingefügt werden muss, können Sie den einzufügenden Speicherort mithilfe der binären Suche finden. Verwenden Sie dann memcpy oder ähnliches, um die verbleibenden Elemente in großen Mengen zu kopieren und Platz für das eingefügte Element zu schaffen. Die binäre Suche ist O (log n) und die Kopie ist O (n), was O (n + log n) ergibt. Mit den oben beschriebenen Methoden führen Sie nach jeder Einfügung eine Neusortierung durch, die O (n log n) ist.
Ist das wichtig? Nehmen wir an, Sie fügen zufällig k Elemente ein, wobei k = 1000 ist. Die sortierte Liste umfasst 5000 Elemente.
Binary search + Move = k*(n + log n) = 1000*(5000 + 12) = 5,000,012 = ~5 million ops
Re-sort on each = k*(n log n) = ~60 million ops
Wenn die k Elemente, die eingefügt werden sollen, immer eintreffen, müssen Sie suchen + verschieben. Wenn Sie jedoch eine Liste mit k Elementen erhalten, die Sie vorab in ein sortiertes Array einfügen können, können Sie dies noch besser machen. Sortieren Sie die k Elemente getrennt vom bereits sortierten n-Array. Führen Sie dann eine Scan-Sortierung durch, bei der Sie beide sortierten Arrays gleichzeitig nach unten verschieben und ineinander verschmelzen. - Einstufige Zusammenführungssortierung = k log k + n = 9965 + 5000 = ~ 15.000 Operationen
Update: Bezüglich Ihrer Frage.
First method = binary search+move = O(n + log n)
.Second method = re-sort = O(n log n)
Erklärt genau die Zeiten, die Sie erhalten.quelle
quelle