Tengo n elementos. Por el bien de un ejemplo, digamos, 7 elementos, 1234567. ¡Sé que hay 7! = 5040 permutaciones posibles de estos 7 elementos.
Quiero un algoritmo rápido que comprenda dos funciones:
f (número) asigna un número entre 0 y 5039 a una permutación única, y
f '(permutación) mapea la permutación al número a partir del cual se generó.
No me importa la correspondencia entre número y permutación, siempre que cada permutación tenga su propio número único.
Entonces, por ejemplo, podría tener funciones donde
f(0) = '1234567'
f'('1234567') = 0
El algoritmo más rápido que me viene a la mente es enumerar todas las permutaciones y crear una tabla de búsqueda en ambas direcciones, de modo que, una vez creadas las tablas, f (0) sería O (1) yf ('1234567') sería un búsqueda en una cadena. Sin embargo, esto requiere mucha memoria, particularmente cuando n se vuelve grande.
¿Alguien puede proponer otro algoritmo que funcione rápidamente y sin la desventaja de la memoria?
Respuestas:
Para describir una permutación de n elementos, ves que para la posición en la que termina el primer elemento, tienes n posibilidades, por lo que puedes describir esto con un número entre 0 y n-1. Para la posición en la que termina el siguiente elemento, tiene n-1 posibilidades restantes, por lo que puede describir esto con un número entre 0 y n-2.
Etcétera hasta que tenga n números.
Como ejemplo para n = 5, considere la permutación que trae
abcde
acaebd
.a
, el primer elemento, termina en la segunda posición, por lo que le asignamos el índice 1 .b
termina en la cuarta posición, que sería el índice 3, pero es la tercera que queda, así que le asignamos 2 .c
termina en la primera posición restante, que siempre es 0 .d
termina en la última posición restante, que (de solo dos posiciones restantes) es 1 .e
termina en la única posición restante, indexada en 0 .Entonces tenemos la secuencia de índice {1, 2, 0, 1, 0} .
Ahora sabe que, por ejemplo, en un número binario, 'xyz' significa z + 2y + 4x. Para un número decimal,
es z + 10y + 100x. Cada dígito se multiplica por algún peso y se suman los resultados. El patrón obvio en el peso es, por supuesto, que el peso es w = b ^ k, con b la base del número yk el índice del dígito. (Siempre contaré los dígitos desde la derecha y comenzando en el índice 0 para el dígito más a la derecha. Del mismo modo, cuando hablo del 'primer' dígito, me refiero al más a la derecha).
La razón por la que los pesos de los dígitos siguen este patrón es que el número más alto que se puede representar con los dígitos del 0 al k debe ser exactamente 1 más bajo que el número más bajo que se puede representar usando solo el dígito k + 1. En binario, 0111 debe ser uno menor que 1000. En decimal, 099999 debe ser uno menor que 100000.
Codificación a base variable
La regla importante es que el espaciado entre números subsiguientes sea exactamente 1. Al darnos cuenta de esto, podemos representar nuestra secuencia de índice mediante un número de base variable . La base de cada dígito es la cantidad de posibilidades diferentes para ese dígito. Para decimal, cada dígito tiene 10 posibilidades, para nuestro sistema el dígito más a la derecha tendría 1 posibilidad y el más a la izquierda tendrá n posibilidades. Pero como el dígito más a la derecha (el último número de nuestra secuencia) es siempre 0, lo dejamos fuera. Eso significa que nos quedamos con las bases 2 an. En general, el dígito k 'tendrá la base b [k] = k + 2. El valor más alto permitido para el dígito k es h [k] = b [k] - 1 = k + 1.
Nuestra regla sobre los pesos w [k] de dígitos requiere que la suma de h [i] * w [i], donde i va de i = 0 a i = k, sea igual a 1 * w [k + 1]. Dicho de forma recurrente, w [k + 1] = w [k] + h [k] * w [k] = w [k] * (h [k] + 1). El primer peso w [0] siempre debe ser 1. A partir de ahí, tenemos los siguientes valores:
(La relación general w [k-1] = k! Se demuestra fácilmente por inducción).
El número que obtenemos al convertir nuestra secuencia será la suma de s [k] * w [k], con k corriendo de 0 a n-1. Aquí s [k] es el k'th (más a la derecha, comenzando en 0) elemento de la secuencia. Como ejemplo, tomemos nuestro {1, 2, 0, 1, 0}, con el elemento más a la derecha eliminado como se mencionó anteriormente: {1, 2, 0, 1} . Nuestra suma es 1 * 1 + 0 * 2 + 2 * 6 + 1 * 24 = 37 .
Tenga en cuenta que si tomamos la posición máxima para cada índice, tendríamos {4, 3, 2, 1, 0}, y eso se convierte en 119. Dado que los pesos en nuestra codificación numérica se eligieron para no omitir cualquier número, todos los números del 0 al 119 son válidos. Hay precisamente 120 de estos, que es n! para n = 5 en nuestro ejemplo, precisamente el número de permutaciones diferentes. Para que pueda ver que nuestros números codificados especifican completamente todas las permutaciones posibles.
La decodificación desde la decodificación de base variable
es similar a la conversión a binario o decimal. El algoritmo común es este:
Para nuestro número de base variable:
Esto decodifica correctamente nuestro 37 de nuevo a {1, 2, 0, 1} (
sequence
estaría{1, 0, 2, 1}
en este ejemplo de código, pero lo que sea ... siempre que indexe adecuadamente). Solo necesitamos agregar 0 en el extremo derecho (recuerde que el último elemento siempre tiene solo una posibilidad para su nueva posición) para recuperar nuestra secuencia original {1, 2, 0, 1, 0}.Permutación de una lista usando una secuencia de índice
Puede usar el siguiente algoritmo para permutar una lista de acuerdo con una secuencia de índice específica. Es un algoritmo O (n²), desafortunadamente.
Representación común de permutaciones
Normalmente, no representaría una permutación tan poco intuitiva como lo hemos hecho, sino simplemente por la posición absoluta de cada elemento después de que se aplica la permutación. Nuestro ejemplo {1, 2, 0, 1, 0} para
abcde
acaebd
normalmente está representado por {1, 3, 0, 4, 2}. Cada índice de 0 a 4 (o en general, de 0 a n-1) ocurre exactamente una vez en esta representación.Aplicar una permutación en esta forma es fácil:
Invertirlo es muy similar:
Conversión de nuestra representación a la representación común
Tenga en cuenta que si tomamos nuestro algoritmo para permutar una lista usando nuestra secuencia de índice y lo aplicamos a la permutación de identidad {0, 1, 2, ..., n-1}, obtenemos el permutación inversa , representada en la forma común. ( {2, 0, 4, 1, 3} en nuestro ejemplo).
Para obtener la premutación no invertida, aplicamos el algoritmo de permutación que acabo de mostrar:
O simplemente puede aplicar la permutación directamente, utilizando el algoritmo de permutación inversa:
Tenga en cuenta que todos los algoritmos para tratar las permutaciones en la forma común son O (n), mientras que aplicar una permutación en nuestra forma es O (n²). Si necesita aplicar una permutación varias veces, primero conviértala a la representación común.
fuente
1234
, f (4) = {0, 2, 0, 0} = 1342. Y f '(1423) = {0, 1 1, 0} = 3. Este algoritmo es realmente inspirador. Me pregunto si es el trabajo original del OP. Lo he estudiado y analizado durante un tiempo. Y creo que es correcto :){1, 2, 0, 1, 0}
->{1, 3, 0, 4, 2}
? ¿Y viceversa? ¿Es posible? (al no convertir entre{1, 2, 0, 1, 0}
<-->{C, A, E, B, D}
, que necesita O (n ^ 2).) Si "nuestro estilo" y "estilo común" no son convertibles, de hecho son dos cosas distintas, ¿no es así? Gracias xEncontré un algoritmo O (n), aquí hay una breve explicación http://antoinecomeau.blogspot.ca/2014/07/mapping-between-permutations-and.html
fuente
La complejidad se puede reducir a n * log (n), consulte la sección 10.1.1 ("El código de Lehmer (tabla de inversión)", p.232ff) del fxtbook: http://www.jjj.de/fxt/ #fxtbook pase a la sección 10.1.1.1 ("Computación con arreglos grandes" p.235) para el método rápido. El código (GPL, C ++) está en la misma página web.
fuente
Problema resuelto. Sin embargo, no estoy seguro de que sigas necesitando la solución después de estos años. LOL, me acabo de unir a este sitio, así que ... Revisa mi clase de permutación de Java. Puede basarse en un índice para obtener una permutación de símbolo, o dar una permutación de símbolo y luego obtener el índice.
Aquí está mi clase de premutación
y aquí está mi clase principal para mostrar cómo usar la clase.
Que te diviertas. :)
fuente
Cada elemento puede estar en una de siete posiciones. Para describir la posición de un elemento, necesitaría tres bits. Eso significa que puede almacenar la posición de todos los elementos en un valor de 32 bits. Eso está lejos de ser eficiente, ya que esta representación incluso permitiría que todos los elementos estén en la misma posición, pero creo que el enmascaramiento de bits debería ser razonablemente rápido.
Sin embargo, con más de 8 posiciones necesitarás algo más ingenioso.
fuente
Esto pasa a ser una función incorporada en J :
fuente
Puede codificar permutaciones utilizando un algoritmo recursivo. Si una N-permutación (algún orden de los números {0, .., N-1}) tiene la forma {x, ...} entonces codifíquela como x + N * la codificación del (N-1) -permutación representada por "..." en los números {0, N-1} - {x}. Suena como un bocado, aquí hay un código:
Este algoritmo es O (n ^ 2). Puntos de bonificación si alguien tiene un algoritmo O (n).
fuente
¡Qué pregunta tan interesante!
Si todos sus elementos son números, es posible que desee considerar convertirlos de cadenas en números reales. Entonces podrá ordenar todas las permutaciones colocándolas en orden y colocándolas en una matriz. Después de eso, estará abierto a cualquiera de los diversos algoritmos de búsqueda que existen.
fuente
Me apresuré en mi respuesta anterior (eliminada), aunque tengo la respuesta real. Lo proporciona un concepto similar, el factorádico , y está relacionado con las permutaciones (mi respuesta se relaciona con las combinaciones, pido disculpas por esa confusión). Odio publicar enlaces de wikipedia, pero lo que escribí hace un tiempo es ininteligible por alguna razón. Entonces, puedo ampliar esto más adelante si se solicita.
fuente
Hay un libro escrito sobre esto. Lo siento, pero no recuerdo el nombre (probablemente lo encontrará en wikipedia). pero de todos modos escribí una implementación de Python de ese sistema de enumeración: http://kks.cabal.fi/Kombinaattori Parte de ella está en finlandés, pero solo copie el código y las variables de nombre ...
fuente
Tenía esta pregunta exacta y pensé en proporcionar mi solución de Python. Es O (n ^ 2).
Es bastante sencillo; después de generar la representación factorádica del número, solo selecciono y elimino los caracteres de la cadena. Eliminar de la cadena es la razón por la que esta es una solución O (n ^ 2).
La solución de Antoine es mejor para el rendimiento.
fuente
Una cuestión relacionada es calcular la permutación inversa, una permutación que restaurará los vectores permutados al orden original cuando solo se conoce la matriz de permutación. Aquí está el código O (n) (en PHP):
Software de primavera de David Spector
fuente