Tengo una matriz de JavaScript ordenada y deseo insertar un elemento más en la matriz, de modo que la matriz resultante permanezca ordenada. Ciertamente podría implementar una simple función de inserción de estilo de clasificación rápida:
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));
[ADVERTENCIA] este código tiene un error al intentar insertarlo al comienzo de la matriz, por ejemplo insert(2, [3, 7 ,9]
) produce incorrectos [3, 2, 7, 9].
Sin embargo, noté que las implementaciones de la función Array.sort podrían hacer esto por mí y de forma nativa:
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));
¿Hay una buena razón para elegir la primera implementación sobre la segunda?
Editar : Tenga en cuenta que para el caso general, una inserción de O (log (n)) (como se implementó en el primer ejemplo) será más rápida que un algoritmo de clasificación genérico; sin embargo, este no es necesariamente el caso de JavaScript en particular. Tenga en cuenta que:
- El mejor caso para varios algoritmos de inserción es O (n), que sigue siendo significativamente diferente de O (log (n)), pero no tan malo como O (n log (n)) como se menciona a continuación. Se reduciría al algoritmo de clasificación particular utilizado (ver implementación de Javascript Array.sort? )
- El método de clasificación en JavaScript es una función nativa, por lo que puede obtener enormes beneficios: O (log (n)) con un coeficiente enorme puede ser mucho peor que O (n) para conjuntos de datos de tamaño razonable.
fuente
splice()
(por ejemplo, su primer ejemplo) ya es O (n). Incluso si no crea internamente una nueva copia de toda la matriz, potencialmente tiene que desviar todos los n elementos hacia atrás 1 posición si el elemento se va a insertar en la posición 0. Tal vez sea rápido porque es una función nativa y la constante es bajo, pero es O (n) de todos modos.parseInt
usoMath.floor
en su lugar.Math.floor
es mucho más rápido queparseInt
: jsperf.com/test-parseint-and-math-floorRespuestas:
Solo como un único punto de datos, para las patadas probé esto insertando 1000 elementos aleatorios en una matriz de 100,000 números ordenados previamente usando los dos métodos que usan Chrome en Windows 7:
Entonces, al menos en esta configuración, el método nativo no lo compensa. Esto es cierto incluso para pequeños conjuntos de datos, insertando 100 elementos en una matriz de 1000:
fuente
Array.prototype.sort
, pierde los beneficios de C ++ porque la función JS se llama mucho.Simple ( Demo ):
fuente
x >>> 1
es un desplazamiento binario a la derecha por 1 posición, que efectivamente es solo una división por 2. por ejemplo, para 11:1011
->101
resultados a 5.>>> 1
y ( visto aquí y allá )>> 1
?>>>
es un desplazamiento a la derecha sin signo, mientras que>>
se extiende al signo: todo se reduce a la representación en memoria de números negativos, donde el bit alto se establece si es negativo. Entonces, si cambias0b1000
1 lugar a la derecha con>>
, obtendrás0b1100
, si en cambio lo>>>
usas, obtendrás0b0100
. Si bien en el caso dado en la respuesta en realidad no importa (el número que se está cambiando no debe ser mayor que el valor máximo de un entero positivo de 32 bits con signo ni negativo), es importante usar el correcto en esos dos casos (usted necesita elegir qué caso necesita manejar).0b1000
derecha 1 lugar con>>
lo que obtendrás0b1100
". No, lo entiendes0b0100
. El resultado de los diferentes operadores de desplazamiento a la derecha será el mismo para todos los valores, excepto los números negativos y los números mayores que 2 ^ 31 (es decir, números con un 1 en el primer bit).Muy buena y notable pregunta con una discusión muy interesante! También estaba usando el
Array.sort()
función después de empujar un solo elemento en una matriz con algunos miles de objetos.Tuve que extender su
locationOf
función para mi propósito debido a que tenía objetos complejos y, por lo tanto, la necesidad de una función de comparación como enArray.sort()
:fuente
return c == -1 ? pivot : pivot + 1;
para devolver el índice correcto. De lo contrario, para una matriz con longitud 1, la función devolvería -1 o 0.>> 1
debería ser más rápido (o no más lento) que/ 2
comparer
función. En este algoritmo se compara+-1
pero podría ser un valor arbitrario<0
/>0
. Ver función de comparación . La parte problemática no es solo laswitch
declaración, sino también la línea:if (end - start <= 1) return c == -1 ? pivot - 1 : pivot;
donde tambiénc
se compara-1
.Hay un error en tu código. Debería leer:
Sin esta solución, el código nunca podrá insertar un elemento al comienzo de la matriz.
fuente
Sé que esta es una vieja pregunta que ya tiene una respuesta, y hay varias otras respuestas decentes. Veo algunas respuestas que proponen que puede resolver este problema buscando el índice de inserción correcto en O (log n); puede, pero no puede insertar en ese momento, porque la matriz debe copiarse parcialmente para hacer espacio.
En pocas palabras: si realmente necesita O (log n) inserta y elimina en una matriz ordenada, necesita una estructura de datos diferente, no una matriz. Deberías usar un B-Tree . Las ganancias de rendimiento que obtendrá al usar un B-Tree para un conjunto de datos grande, eclipsarán cualquiera de las mejoras que se ofrecen aquí.
Si debe usar una matriz. Ofrezco el siguiente código, basado en el tipo de inserción, que funciona, si y solo si la matriz ya está ordenada. Esto es útil para el caso cuando necesita recurrir después de cada inserción:
Debería funcionar en O (n), que creo que es lo mejor que puedes hacer. Sería mejor si js admitiera la asignación múltiple. Aquí hay un ejemplo para jugar:
Actualizar:
esto podría ser más rápido:
Enlace JS Bin actualizado
fuente
Su función de inserción supone que la matriz dada está ordenada, busca directamente la ubicación donde se puede insertar el nuevo elemento, generalmente simplemente mirando algunos de los elementos en la matriz.
La función de clasificación general de una matriz no puede tomar estos atajos. Obviamente, al menos tiene que inspeccionar todos los elementos de la matriz para ver si ya están ordenados correctamente. Este hecho por sí solo hace que la ordenación general sea más lenta que la función de inserción.
Un algoritmo de ordenación genérico generalmente es O (n ⋅ log (n)) en promedio y, dependiendo de la implementación, podría ser el peor de los casos si la matriz ya está ordenada, lo que lleva a complejidades de O (n 2 ) . La búsqueda directa de la posición de inserción tiene una complejidad de O (log (n)) , por lo que siempre será mucho más rápido.
fuente
Para una pequeña cantidad de artículos, la diferencia es bastante trivial. Sin embargo, si está insertando una gran cantidad de elementos o trabajando con una matriz muy grande, llamar a .sort () después de cada inserción causará una gran cantidad de sobrecarga.
Terminé escribiendo una función de búsqueda / inserción binaria bastante hábil para este propósito exacto, así que pensé en compartirla. Dado que utiliza un
while
bucle en lugar de recurrencia, no se escuchan llamadas de funciones adicionales, por lo que creo que el rendimiento será incluso mejor que cualquiera de los métodos publicados originalmente. Y emula elArray.sort()
comparador predeterminado de forma predeterminada, pero acepta una función de comparación personalizada si lo desea.Si está abierto a usar otras bibliotecas, lodash proporciona las funciones sortedIndex y sortedLastIndex , que podrían usarse en lugar del
while
bucle. Las dos desventajas potenciales son 1) el rendimiento no es tan bueno como mi método (aunque no estoy seguro de cuánto peor es) y 2) no acepta una función de comparación personalizada, solo un método para obtener el valor para comparar (usando el comparador predeterminado, supongo).fuente
arr.splice()
es seguramente O (n) complejidad de tiempo.Aquí hay algunos pensamientos: en primer lugar, si está realmente preocupado por el tiempo de ejecución de su código, ¡asegúrese de saber qué sucede cuando llama a las funciones integradas! No sé desde arriba en JavaScript, pero un rápido google de la función de empalme devolvió esto , lo que parece indicar que estás creando una matriz completamente nueva en cada llamada. No sé si realmente importa, pero ciertamente está relacionado con la eficiencia. Veo que Breton, en los comentarios, ya lo ha señalado, pero ciertamente cumple con cualquier función de manipulación de matriz que elija.
De todos modos, en realidad resolviendo el problema.
Cuando leí que querías ordenar, ¡mi primer pensamiento es usar el método de inserción! . Es útil porque se ejecuta en tiempo lineal en listas ordenadas o casi ordenadas . Como sus matrices tendrán solo 1 elemento fuera de orden, eso cuenta como casi ordenado (excepto, bueno, matrices de tamaño 2 o 3 o lo que sea, pero en ese punto, vamos). Ahora, implementar el tipo no es tan malo, pero es una molestia con la que no querrás lidiar, y de nuevo, no sé nada sobre JavaScript y si será fácil o difícil o no. Esto elimina la necesidad de su función de búsqueda, y simplemente presiona (como Breton sugirió).
En segundo lugar, su función de búsqueda "quicksort-esque" parece ser un algoritmo de búsqueda binaria . Es un algoritmo muy agradable, intuitivo y rápido, pero con un inconveniente: es muy difícil de implementar correctamente. No me atreveré a decir si el tuyo es correcto o no (¡espero que lo sea, por supuesto! :)), pero ten cuidado si quieres usarlo.
De todos modos, resumen: el uso de "push" con clasificación de inserción funcionará en tiempo lineal (suponiendo que el resto de la matriz esté ordenado) y evitará cualquier requisito de algoritmo de búsqueda binaria desordenado. No sé si esta es la mejor manera (implementación subyacente de matrices, tal vez una función incorporada loca lo hace mejor, quién sabe), pero me parece razonable. :) - Agor.
fuente
splice()
ya es O (n). Incluso si no crea internamente una nueva copia de toda la matriz, que tiene, potencialmente, para desviar todos los artículos de vuelta n 1 posición si el elemento se va a insertar en la posición 0.Aquí hay una comparación de cuatro algoritmos diferentes para lograr esto: https://jsperf.com/sorted-array-insert-comparison/1
Algoritmos
La ingenuidad siempre es horrible. Parece que para tamaños de matriz pequeños, los otros tres no difieren demasiado, pero para matrices más grandes, los últimos 2 superan el enfoque lineal simple.
fuente
Aquí hay una versión que usa lodash.
nota: sortedIndex realiza una búsqueda binaria.
fuente
La mejor estructura de datos que se me ocurre es una lista de salto indexada que mantiene las propiedades de inserción de las listas vinculadas con una estructura jerárquica que permite las operaciones de tiempo de registro. En promedio, las búsquedas, la inserción y las búsquedas de acceso aleatorio se pueden realizar en tiempo O (log n).
Un árbol de estadísticas de orden permite la indexación del tiempo de registro con una función de clasificación.
Si no necesita acceso aleatorio pero necesita la inserción O (log n) y la búsqueda de claves, puede deshacerse de la estructura de la matriz y usar cualquier tipo de árbol de búsqueda binario .
Ninguna de las respuestas que usa
array.splice()
son eficientes en absoluto, ya que eso es en promedio el tiempo O (n).¿Cuál es la complejidad temporal de array.splice () en Google Chrome?fuente
Is there a good reason to choose [splice into location found] over [push & sort]?
Aquí está mi función, usa la búsqueda binaria para encontrar el elemento y luego la inserta de manera apropiada:
fuente
No vuelva a ordenar después de cada artículo, es excesivo ...
Si solo hay un elemento para insertar, puede encontrar la ubicación para insertar mediante la búsqueda binaria. Luego use memcpy o similar para copiar en masa los elementos restantes para hacer espacio para el insertado. La búsqueda binaria es O (log n), y la copia es O (n), dando O (n + log n) total. Usando los métodos anteriores, está haciendo una reordenación después de cada inserción, que es O (n log n).
¿Importa? Digamos que está insertando aleatoriamente k elementos, donde k = 1000. La lista ordenada es de 5000 elementos.
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
Si los k elementos para insertar llegan cada vez, entonces debe hacer buscar + mover. Sin embargo, si se le proporciona una lista de k elementos para insertar en una matriz ordenada, con anticipación, puede hacerlo aún mejor. Ordene los k elementos, por separado de la matriz n ya ordenada. Luego, haga una ordenación de exploración, en la que mueve hacia abajo ambas matrices ordenadas simultáneamente, fusionando una en la otra. - Clasificación de fusión en un paso = k log k + n = 9965 + 5000 = ~ 15,000 operaciones
Actualización: con respecto a su pregunta.
First method = binary search+move = O(n + log n)
.Second method = re-sort = O(n log n)
Explica exactamente los tiempos que está recibiendo.fuente
fuente