Así que aquí está la solución O (n log n) en java.
long merge(int[] arr, int[] left, int[] right) {
int i = 0, j = 0, count = 0;
while (i < left.length || j < right.length) {
if (i == left.length) {
arr[i+j] = right[j];
j++;
} else if (j == right.length) {
arr[i+j] = left[i];
i++;
} else if (left[i] <= right[j]) {
arr[i+j] = left[i];
i++;
} else {
arr[i+j] = right[j];
count += left.length-i;
j++;
}
}
return count;
}
long invCount(int[] arr) {
if (arr.length < 2)
return 0;
int m = (arr.length + 1) / 2;
int left[] = Arrays.copyOfRange(arr, 0, m);
int right[] = Arrays.copyOfRange(arr, m, arr.length);
return invCount(left) + invCount(right) + merge(arr, left, right);
}
Este es un tipo de combinación casi normal, toda la magia está oculta en la función de combinación. Tenga en cuenta que al ordenar el algoritmo, elimine las inversiones. Mientras que el algoritmo de fusión cuenta el número de inversiones eliminadas (solucionadas, se podría decir).
El único momento en que se eliminan las inversiones es cuando el algoritmo toma un elemento del lado derecho de una matriz y lo fusiona con la matriz principal. El número de inversiones eliminadas por esta operación es el número de elementos que quedan de la matriz de la izquierda para fusionar. :)
Espero que sea lo suficientemente explicativo.
left.length - i
al contador de inversión? Creo que tendría sentido simplemente agregar 1, ya que cayó en el caso lógico donde la comparación entre los dos subarreglos tiene un elemento de arreglo izquierdo más grande que el derecho. ¿Alguien me lo puede explicar como si tuviera 5 años?arr
. Pero no es una inversión. Encontraste inversiones para todos los elementos de la matriz de la izquierda que son mayores que 6. En nuestro caso, también incluye 8. Entonces, se agrega 2count
, que es igual aleft.length - i
.Lo encontré en el tiempo O (n * log n) mediante el siguiente método.
Tome A [1] y encuentre su posición en la matriz ordenada B mediante una búsqueda binaria. El número de inversiones para este elemento será uno menos que el número índice de su posición en B ya que cada número menor que aparezca después del primer elemento de A será una inversión.
2a. acumula el número de inversiones para contrarrestar la variable núm_inversiones.
2b. eliminar A [1] de la matriz A y también de su posición correspondiente en la matriz B
Aquí hay un ejemplo de ejecución de este algoritmo. Matriz original A = (6, 9, 1, 14, 8, 12, 3, 2)
1: Combinar ordenar y copiar a la matriz B
B = (1, 2, 3, 6, 8, 9, 12, 14)
2: Toma A [1] y búsqueda binaria para encontrarlo en la matriz B
A [1] = 6
B = (1, 2, 3, 6 , 8, 9, 12, 14)
6 está en la 4ª posición de la matriz B, por lo que hay 3 inversiones. Sabemos esto porque 6 estaba en la primera posición en la matriz A, por lo tanto, cualquier elemento de valor menor que aparezca posteriormente en la matriz A tendría un índice de j> i (ya que i en este caso es 1).
2.b: Elimine A [1] de la matriz A y también de su posición correspondiente en la matriz B (se eliminan los elementos en negrita).
A = ( 6, 9, 1, 14, 8, 12, 3, 2) = (9, 1, 14, 8, 12, 3, 2)
B = (1, 2, 3, 6, 8, 9, 12, 14) = (1, 2, 3, 8, 9, 12, 14)
3: Vuelva a ejecutar desde el paso 2 en las nuevas matrices A y B.
A [1] = 9
B = (1, 2, 3, 8, 9, 12, 14)
9 está ahora en la quinta posición de la matriz B, por lo que hay 4 inversiones. Sabemos esto porque 9 estaba en la primera posición en la matriz A, por lo tanto, cualquier elemento de menor valor que aparezca posteriormente tendría un índice de j> i (ya que i en este caso es nuevamente 1). Elimine A [1] de la matriz A y también de su posición correspondiente en la matriz B (se eliminan los elementos en negrita)
A = ( 9 , 1, 14, 8, 12, 3, 2) = (1, 14, 8, 12, 3, 2)
B = (1, 2, 3, 8, 9 , 12, 14) = (1, 2, 3, 8, 12, 14)
Continuar en esta línea nos dará el número total de inversiones para la matriz A una vez que se complete el ciclo.
El paso 1 (combinación de clasificación) requeriría O (n * log n) para ejecutarse. El paso 2 se ejecutaría n veces y en cada ejecución realizaría una búsqueda binaria que toma O (log n) para ejecutarse para un total de O (n * log n). El tiempo de ejecución total sería entonces O (n * log n) + O (n * log n) = O (n * log n).
Gracias por tu ayuda. Escribir las matrices de muestra en una hoja de papel realmente ayudó a visualizar el problema.
fuente
En Python
fuente
Me pregunto por qué nadie mencionó todavía los árboles indexados en binarios . Puede usar uno para mantener sumas de prefijo en los valores de sus elementos de permutación. Luego, puede proceder de derecha a izquierda y contar para cada elemento el número de elementos más pequeño que el de la derecha:
La complejidad es O (n log n) y el factor constante es muy bajo.
fuente
i -= i & -i
línea? Y de manera similari += i & -i
timeit
compara todas las respuestas de Python a esta pregunta, por lo que incluye su código. Es posible que le interese ver los resultados de sincronización.En realidad, tenía una pregunta similar a esta para la tarea. Me restringieron que debe tener eficiencia O (nlogn).
Usé la idea que propuso de usar Mergesort, ya que ya tiene la eficiencia correcta. Acabo de insertar un código en la función de fusión que era básicamente: Siempre que se agrega un número de la matriz de la derecha a la matriz de salida, agrego al número total de inversiones, la cantidad de números que quedan en la matriz de la izquierda.
Esto tiene mucho sentido para mí ahora que lo he pensado lo suficiente. Estás contando cuántas veces hay un número mayor antes de cualquier número.
hth.
fuente
El propósito principal de esta respuesta es comparar las velocidades de las diversas versiones de Python que se encuentran aquí, pero también tengo algunas contribuciones propias. (FWIW, acabo de descubrir esta pregunta mientras realizaba una búsqueda duplicada).
Las velocidades de ejecución relativas de los algoritmos implementados en CPython pueden ser diferentes a las que cabría esperar de un simple análisis de los algoritmos y de la experiencia con otros lenguajes. Esto se debe a que Python proporciona muchas funciones y métodos potentes implementados en C que pueden operar en listas y otras colecciones a una velocidad cercana a la que se obtendría en un lenguaje completamente compilado, por lo que esas operaciones se ejecutan mucho más rápido que los algoritmos equivalentes implementados "manualmente" con Python. código.
El código que aprovecha estas herramientas a menudo puede superar a los algoritmos teóricamente superiores que intentan hacer todo con las operaciones de Python en elementos individuales de la colección. Por supuesto, la cantidad real de datos que se procesan también influye en esto. Pero para cantidades moderadas de datos, el código que utiliza un algoritmo O (n²) que se ejecuta a velocidad C puede superar fácilmente a un algoritmo O (n log n) que realiza la mayor parte de su trabajo con operaciones individuales de Python.
Muchas de las respuestas publicadas a esta pregunta de recuento de inversiones utilizan un algoritmo basado en mergesort. En teoría, este es un buen enfoque, a menos que el tamaño de la matriz sea muy pequeño. Pero el TimSort integrado de Python (un algoritmo de clasificación estable híbrido, derivado de la clasificación por combinación y la clasificación por inserción) se ejecuta a la velocidad de C, y una clasificación por combinación codificada a mano en Python no puede competir con él por la velocidad.
Una de las soluciones más intrigantes aquí, en la respuesta publicada por Niklas B , utiliza la ordenación incorporada para determinar la clasificación de los elementos de la matriz y un árbol indexado binario (también conocido como árbol de Fenwick) para almacenar las sumas acumulativas necesarias para calcular la inversión contar. En el proceso de intentar comprender esta estructura de datos y el algoritmo de Niklas, escribí algunas variaciones propias (publicadas a continuación). Pero también descubrí que para tamaños de lista moderados es más rápido usar la función incorporada de Python
sum
que el encantador árbol de Fenwick.Finalmente, cuando el tamaño de la lista se acerca a 500, el aspecto O (n²) de las llamadas
sum
dentro de esefor
bucle asoma su fea cabeza y el rendimiento comienza a caer en picado.Mergesort no es el único tipo O (nlogn), y se pueden utilizar varios otros para realizar el recuento de inversiones. La respuesta de prasadvk usa un tipo de árbol binario, sin embargo, su código parece estar en C ++ o uno de sus derivados. Entonces agregué una versión de Python. Originalmente usé una clase para implementar los nodos del árbol, pero descubrí que un dictado es notablemente más rápido. Finalmente usé list, que es aún más rápido, aunque hace que el código sea un poco menos legible.
Una ventaja de treesort es que es mucho más fácil de implementar de forma iterativa que mergesort. Python no optimiza la recursividad y tiene un límite de profundidad de recursividad (aunque eso se puede aumentar si realmente lo necesita). Y, por supuesto, las llamadas a funciones de Python son relativamente lentas, por lo que cuando intentas optimizar la velocidad es bueno evitar las llamadas a funciones, cuando sea práctico.
Otro tipo O (nlogn) es el venerable tipo radix. Su gran ventaja es que no compara claves entre sí. Su desventaja es que funciona mejor en secuencias contiguas de enteros, idealmente una permutación de enteros en
range(b**m)
dondeb
generalmente es 2. Agregué algunas versiones basadas en ordenamiento por base después de intentar leer Inversiones de conteo, Conteo de rango ortogonal sin conexión y Problemas relacionados, que es vinculado en el cálculo del número de "inversiones" en una permutación .Para usar la ordenación por radix de manera efectiva para contar inversiones en una secuencia general
seq
de longitud n, podemos crear una permutación derange(n)
que tenga el mismo número de inversiones queseq
. Podemos hacer eso en (en el peor de los casos) O (nlogn) tiempo a través de TimSort. El truco consiste en permutar los índices deseq
ordenandoseq
. Es más fácil explicar esto con un pequeño ejemplo.salida
Al ordenar los pares (valor, índice) de
seq
, hemos permutado los índices deseq
con el mismo número de intercambios que se requieren para colocarseq
en su orden original a partir de su orden ordenado. Podemos crear esa permutación ordenandorange(n)
con una función clave adecuada:salida
Podemos evitar eso
lambda
usandoseq
el.__getitem__
método de:Esto es solo un poco más rápido, pero estamos buscando todas las mejoras de velocidad que podamos obtener. ;)
El siguiente código realiza
timeit
pruebas en todos los algoritmos de Python existentes en esta página, además de algunos de los míos: un par de versiones O (n²) de fuerza bruta, algunas variaciones del algoritmo de Niklas B y, por supuesto, una basada en mergesort. (que escribí sin referirme a las respuestas existentes). También tiene mi código de clasificación de árboles basado en listas derivado aproximadamente del código de prasadvk, y varias funciones basadas en la clasificación de radix, algunas usando una estrategia similar a los enfoques de ordenación combinada y otras usandosum
un árbol de Fenwick.Este programa mide el tiempo de ejecución de cada función en una serie de listas aleatorias de enteros; también puede verificar que cada función dé los mismos resultados que las demás y que no modifique la lista de entrada.
Cada
timeit
llamada da un vector que contiene 3 resultados, que clasifico. El valor principal a considerar aquí es el mínimo, los otros valores simplemente dan una indicación de cuán confiable es ese valor mínimo, como se explica en la Nota en lostimeit
documentos del módulo .Desafortunadamente, el resultado de este programa es demasiado grande para incluirlo en esta respuesta, por lo que lo estoy publicando en su propia respuesta (wiki de la comunidad) .
El resultado es de 3 ejecuciones en mi antigua máquina de 2 GHz de un solo núcleo de 32 bits que ejecuta Python 3.6.0 en una antigua distribución derivada de Debian. YMMV. Durante las pruebas, apagué mi navegador web y me desconecté de mi enrutador para minimizar el impacto de otras tareas en la CPU.
La primera ejecución prueba todas las funciones con tamaños de lista de 5 a 320, con tamaños de bucle de 4096 a 64 (a medida que el tamaño de la lista se duplica, el tamaño del bucle se reduce a la mitad). El grupo aleatorio utilizado para construir cada lista es la mitad del tamaño de la lista en sí, por lo que es probable que obtengamos muchos duplicados. Algunos de los algoritmos de recuento de inversiones son más sensibles a los duplicados que otros.
La segunda ejecución utiliza listas más grandes: 640 a 10240 y un tamaño de bucle fijo de 8. Para ahorrar tiempo, elimina varias de las funciones más lentas de las pruebas. Mi fuerza bruta O (N ²) funciones son sólo forma demasiado lenta en estos tamaños, y como se mencionó anteriormente, mi código que utiliza
sum
, lo que hace tan bien en las pequeñas y listas moderadas, simplemente no puede mantenerse al tanto de las listas grandes.La ejecución final cubre los tamaños de lista de 20480 a 655360, y un tamaño de bucle fijo de 4, con las 8 funciones más rápidas. Para tamaños de lista inferiores a 40.000 o más, el código de Tim Babych es el claro ganador. ¡Bien hecho Tim! El código de Niklas B también tiene un buen rendimiento en todos los aspectos, aunque es superado en las listas más pequeñas. El código basado en bisecciones de "python" también funciona bastante bien, aunque parece ser un poco más lento con listas enormes con muchos duplicados, probablemente debido a ese
while
bucle lineal que usa para pasar por encima de los incautos.Sin embargo, para los tamaños de lista muy grandes, los algoritmos basados en bisecciones no pueden competir con los verdaderos algoritmos O (nlogn).
Consulte aquí la salida.
fuente
bisect
es C? Estoy bastante seguro de que es Python.El número de inversiones se puede encontrar analizando el proceso de fusión en orden de fusión:
Al copiar un elemento de la segunda matriz a la matriz de combinación (el 9 en este ejemplo), mantiene su lugar en relación con otros elementos. Al copiar un elemento de la primera matriz a la matriz de combinación (el 5 aquí) se invierte y todos los elementos permanecen en la segunda matriz (2 inversiones con el 3 y el 4). Entonces, una pequeña modificación de la ordenación por fusión puede resolver el problema en O (n ln n).
Por ejemplo, simplemente descomente las dos líneas # en el código de python mergesort a continuación para tener el recuento.
EDITAR 1
La misma tarea se puede lograr con una versión estable de clasificación rápida, conocida por ser un poco más rápida:
Al elegir el pivote como último elemento, las inversiones están bien contadas y el tiempo de ejecución es un 40% mejor que fusionar uno anterior.
EDITAR 2
Para el rendimiento en python, una versión numpy y numba:
Primero la parte numpy, que usa argsort O (n ln n):
Y la parte numba para el enfoque BIT eficiente :
fuente
timeit
compara todas las respuestas de Python a esta pregunta, por lo que incluye su código. Es posible que le interese ver los resultados de sincronización.timeit
colección.Tenga en cuenta que la respuesta de Geoffrey Irving es incorrecta.
Tome la secuencia {3, 2, 1} como ejemplo. Hay tres inversiones: (3, 2), (3, 1), (2, 1), por lo que el número de inversión es 3. Sin embargo, según el método citado, la respuesta habría sido 2.
fuente
Vea esto: http://www.cs.jhu.edu/~xfliu/600.363_F03/hw_solution/solution1.pdf
Espero que le dé la respuesta correcta.
fuente
Aquí hay una posible solución con variación del árbol binario. Agrega un campo llamado rightSubTreeSize a cada nodo del árbol. Siga insertando números en el árbol binario en el orden en que aparecen en la matriz. Si el número va a lhs del nodo, el recuento de inversiones para ese elemento sería (1 + rightSubTreeSize). Dado que todos esos elementos son mayores que el elemento actual y habrían aparecido antes en la matriz. Si el elemento va a rhs de un nodo, simplemente aumente su rightSubTreeSize. A continuación está el código.
fuente
if(p->data < q->data)
contrario, los duplicados no se manejan correctamente. Y no hay necesidad de probarq
en la parte superior del ciclo, unwhile
ciclo incondicional funciona bien. Además, se olvidó de mencionar qué idioma es. :) Y su función parece haber perdido su línea de encabezado.fuente
Dado que esta es una pregunta antigua, daré mi respuesta en C.
fuente
Aquí está la solución de c ++
fuente
Esta respuesta contiene los resultados de las
timeit
pruebas producidas por el código en mi respuesta principal . ¡Consulte esa respuesta para obtener más detalles!fuente
Aquí hay un código C para contar inversiones
Aquí se dio una explicación detallada: http://www.geeksforgeeks.org/counting-inversions/
fuente
O (n log n) tiempo, O (n) solución espacial en java.
Un mergesort, con un ajuste para preservar el número de inversiones realizadas durante el paso de combinación. (para obtener un mergesort bien explicado, consulte http://www.vogella.com/tutorials/JavaAlgorithmsMergesort/article.html )
Dado que se puede realizar la ordenación por fusión en el lugar, la complejidad del espacio se puede mejorar a O (1).
Cuando se usa este tipo, las inversiones ocurren solo en el paso de fusión y solo cuando tenemos que poner un elemento de la segunda parte antes de los elementos de la primera mitad, por ejemplo
combinado con
tenemos 3 + 2 + 0 = 5 inversiones:
Después de haber realizado las 5 inversiones, nuestra nueva lista combinada es 0, 1, 5, 6, 10, 15, 22
Hay una tarea de demostración en Codility llamada ArrayInversionCount, donde puede probar su solución.
fuente
Aquí está la implementación de O (n * log (n)) perl:
fuente
Mi respuesta en Python:
1- Ordene la matriz primero y haga una copia. En mi programa, B representa la matriz ordenada. 2- Itere sobre la matriz original (sin clasificar) y busque el índice de ese elemento en la lista ordenada. También anote el índice del elemento. 3- Asegúrese de que el elemento no tenga duplicados, si los tiene, debe cambiar el valor de su índice en -1. La condición while en mi programa es exactamente eso. 4- Sigue contando la inversión que será tu valor de índice, y quita el elemento una vez que hayas calculado su inversión.
fuente
timeit
compara todas las respuestas de Python a esta pregunta, por lo que incluye su código. Es posible que le interese ver los resultados de sincronización.Bueno, tengo una solución diferente, pero me temo que funcionaría solo para elementos de matriz distintos.
Para explicar mi código, seguimos agregando elementos desde el final de Array.Para cualquier elemento de matriz entrante, encontramos el índice del primer elemento en el vector v que es mayor que nuestro elemento entrante y asignamos ese valor al recuento de inversión del índice del elemento entrante Después de eso, insertamos ese elemento en el vector v en su posición correcta de modo que el vector v permanezca en orden ordenado.
fuente
Otra solución de Python, breve. Hace uso del módulo bisecto incorporado, que proporciona funciones para insertar un elemento en su lugar en la matriz ordenada y para encontrar el índice del elemento en la matriz ordenada.
La idea es almacenar los elementos que quedan del n-ésimo en dicha matriz, lo que nos permitiría encontrar fácilmente el número de ellos mayor que n-ésimo.
fuente
timeit
compara todas las respuestas de Python a esta pregunta, por lo que incluye su código. Es posible que le interese ver los resultados de sincronización. : DLa respuesta fácil de O (n ^ 2) es usar bucles for anidados e incrementar un contador por cada inversión
Ahora supongo que quieres una solución más eficiente, lo pensaré.
fuente
Una posible solución en C ++ que satisfaga el requisito de complejidad de tiempo O (N * log (N)) sería la siguiente.
Se diferencia de una clasificación de combinación normal solo por el contador.
fuente
Aquí está mi solución O (n log n) en Ruby:
Y algunos casos de prueba:
fuente
La mejor manera optimizada será resolverlo a través del tipo de combinación, donde al fusionarse podemos verificar cuántas inversiones se requieren comparando la matriz izquierda y derecha. Siempre que el elemento de la matriz izquierda sea mayor que el elemento de la matriz derecha, será una inversión.
Enfoque de clasificación de fusión: -
Aquí está el código. El código es exactamente el mismo que el ordenamiento combinado, excepto el fragmento de código en el
mergeToParent
método donde estoy contando la inversión en la condición else de(left[leftunPicked] < right[rightunPicked])
Otro enfoque en el que podemos comparar la matriz de entrada con la matriz ordenada: - Esta implementación de Diablo responde. Aunque este no debería ser el enfoque preferido, ya que eliminar los n elementos de una matriz o lista es log (n ^ 2).
fuente
El número máximo de inversiones posibles para una lista de tamaño
n
se puede generalizar mediante una expresión:Entonces, para una matriz de tamaño,
6
las inversiones máximas posibles serán iguales15
.Para lograr una complejidad de
n logn
, podríamos aprovechar el algoritmo de inversión en la ordenación por fusión.Estos son los pasos generalizados:
inversionCount += leftSubArray.length
¡Eso es!
Este es un ejemplo simple, lo hice usando Javascript:
fuente
Implementación de contar inversiones en una matriz con ordenación por fusión en Swift:
Tenga en cuenta que el número de intercambios se incrementa en
(que es la longitud relativa del lado izquierdo de la matriz menos el índice del elemento actual en el lado izquierdo)
... porque ese es el número de elementos que el elemento en el lado derecho de la matriz tuvo que omitir (# de inversiones) para ser ordenados.
fuente
La mayoría de las respuestas se basan en,
MergeSort
pero no es la única forma de resolver esto es enO(nlogn)
Discutiré algunos enfoques.
Usar una
Balanced Binary Search Tree
Algo como esto.
Binary Indexed Tree
Segment Tree
[0, a[i]-1]
y actualizaa[i] with 1
Además, cuando se usa
BIT
oSegment-Tree
una buena idea es hacerCoordinate compression
fuente
C ++ Θ (n lg n) Solución con la impresión de pares que constituyen en cuenta de inversión.
fuente
Use mergesort, en el contador incremental de pasos de combinación si el número copiado en la salida es de la matriz derecha.
fuente
Recientemente tuve que hacer esto en R:
fuente