Eu tenho n elementos. A título de exemplo, digamos, 7 elementos, 1234567. Eu sei que existem 7! = 5040 permutações possíveis destes 7 elementos.
Eu quero um algoritmo rápido compreendendo duas funções:
f (número) mapeia um número entre 0 e 5039 para uma permutação única, e
f '(permutação) mapeia a permutação de volta para o número a partir do qual ela foi gerada.
Eu não me importo com a correspondência entre número e permutação, desde que cada permutação tenha seu próprio número exclusivo.
Então, por exemplo, posso ter funções onde
f(0) = '1234567'
f'('1234567') = 0
O algoritmo mais rápido que vem à mente é enumerar todas as permutações e criar uma tabela de pesquisa em ambas as direções, de modo que, uma vez que as tabelas sejam criadas, f (0) seria O (1) e f ('1234567') seria um pesquisa em uma string. No entanto, isso exige muita memória, principalmente quando n se torna grande.
Alguém pode propor outro algoritmo que funcione rapidamente e sem a desvantagem de memória?
Respostas:
Para descrever uma permutação de n elementos, você vê que para a posição em que o primeiro elemento termina, você tem n possibilidades, portanto, pode descrever isso com um número entre 0 e n-1. Para a posição em que o próximo elemento termina, você tem n-1 possibilidades restantes, então você pode descrever isso com um número entre 0 e n-2.
Et cetera até que você tenha n números.
Como exemplo para n = 5, considere a permutação que traz
abcde
paracaebd
.a
, o primeiro elemento, termina na segunda posição, então atribuímos a ele o índice 1 .b
termina na quarta posição, que seria o índice 3, mas é o terceiro restante, então o atribuímos 2 .c
termina na primeira posição restante, que é sempre 0 .d
termina na última posição restante, que (de apenas duas posições restantes) é 1 .e
termina na única posição restante, indexada em 0 .Portanto, temos a sequência de índice {1, 2, 0, 1, 0} .
Agora você sabe que, por exemplo, em um número binário, 'xyz' significa z + 2y + 4x. Para um número decimal,
é z + 10y + 100x. Cada dígito é multiplicado por algum peso e os resultados são somados. O padrão óbvio do peso é, claro, que o peso é w = b ^ k, com b a base do número ek o índice do dígito. (Sempre contarei os dígitos da direita e começando no índice 0 para o dígito mais à direita. Da mesma forma, quando falo sobre o 'primeiro' dígito, quero dizer o mais à direita.)
A razão pela qual os pesos dos dígitos seguem esse padrão é que o número mais alto que pode ser representado pelos dígitos de 0 a k deve ser exatamente 1 menor do que o número mais baixo que pode ser representado usando apenas o dígito k + 1. Em binário, 0111 deve ser menor que 1000. Em decimal, 099999 deve ser menor que 100000.
Codificação para base variável
O espaçamento entre os números subsequentes sendo exatamente 1 é a regra importante. Percebendo isso, podemos representar nossa sequência de índice por um número de base variável . A base para cada dígito é a quantidade de possibilidades diferentes para aquele dígito. Para decimal, cada dígito tem 10 possibilidades, para nosso sistema o dígito mais à direita teria 1 possibilidade e o mais à esquerda teria n possibilidades. Mas como o dígito mais à direita (o último número em nossa sequência) é sempre 0, nós o deixamos de fora. Isso significa que ficamos com as bases 2 a n. Em geral, o k'ésimo dígito terá base b [k] = k + 2. O maior valor permitido para o dígito k é h [k] = b [k] - 1 = k + 1.
Nossa regra sobre os pesos w [k] dos dígitos requer que a soma de h [i] * w [i], onde i vai de i = 0 a i = k, seja igual a 1 * w [k + 1]. Declarado de forma recorrente, w [k + 1] = w [k] + h [k] * w [k] = w [k] * (h [k] + 1). O primeiro peso w [0] deve ser sempre 1. A partir daí, temos os seguintes valores:
(A relação geral w [k-1] = k! É facilmente provada por indução.)
O número que obtemos ao converter nossa sequência será então a soma de s [k] * w [k], com k variando de 0 a n-1. Aqui s [k] é o elemento k'th (mais à direita, começando em 0) da sequência. Como exemplo, tome nosso {1, 2, 0, 1, 0}, com o elemento mais à direita retirado conforme mencionado antes: {1, 2, 0, 1} . Nossa soma é 1 * 1 + 0 * 2 + 2 * 6 + 1 * 24 = 37 .
Observe que se tomarmos a posição máxima para cada índice, teremos {4, 3, 2, 1, 0}, e isso se converte em 119. Uma vez que os pesos em nossa codificação numérica foram escolhidos para que não pulemos quaisquer números, todos os números de 0 a 119 são válidos. Existem precisamente 120 deles, que é n! para n = 5 em nosso exemplo, exatamente o número de permutações diferentes. Portanto, você pode ver nossos números codificados especificando completamente todas as permutações possíveis.
Decodificação de base variável A
decodificação é semelhante à conversão para binário ou decimal. O algoritmo comum é este:
Para nosso número de base variável:
Isso decodifica corretamente nosso 37 de volta para {1, 2, 0, 1} (
sequence
seria{1, 0, 2, 1}
neste exemplo de código, mas tanto faz ... contanto que você indexe adequadamente). Precisamos apenas adicionar 0 na extremidade direita (lembre-se de que o último elemento sempre tem apenas uma possibilidade para sua nova posição) para recuperar nossa sequência original {1, 2, 0, 1, 0}.Permutando uma lista usando uma sequência de índice
Você pode usar o algoritmo a seguir para permutar uma lista de acordo com uma sequência de índice específica. É um algoritmo O (n²), infelizmente.
Representação comum de permutações
Normalmente, você não representaria uma permutação de forma tão não intuitiva como fizemos, mas simplesmente pela posição absoluta de cada elemento após a aplicação da permutação. Nosso exemplo {1, 2, 0, 1, 0} para
abcde
tocaebd
é normalmente representado por {1, 3, 0, 4, 2}. Cada índice de 0 a 4 (ou em geral, 0 a n-1) ocorre exatamente uma vez nesta representação.Aplicar uma permutação neste formulário é fácil:
Invertê-lo é muito semelhante:
Convertendo de nossa representação para a representação comum
Observe que se pegarmos nosso algoritmo para permutar uma lista usando nossa sequência de índice e aplicá-lo à permutação de identidade {0, 1, 2, ..., n-1}, obteremos o permutação inversa , representada na forma comum. ( {2, 0, 4, 1, 3} em nosso exemplo).
Para obter a pré-mutação não invertida, aplicamos o algoritmo de permutação que acabei de mostrar:
Ou você pode apenas aplicar a permutação diretamente, usando o algoritmo de permutação inversa:
Observe que todos os algoritmos para lidar com permutações na forma comum são O (n), enquanto a aplicação de uma permutação em nossa forma é O (n²). Se você precisar aplicar uma permutação várias vezes, primeiro converta-a para a representação comum.
fonte
1234
, f (4) = {0, 2, 0, 0} = 1342. E f '(1423) = {0, 1 1, 0} = 3. Este algoritmo é realmente inspirador. Eu me pergunto se é o trabalho original do OP. eu estudei e analisei por um tempo. E eu acredito que está correto :){1, 2, 0, 1, 0}
->{1, 3, 0, 4, 2}
? E vice versa? É possível? (por não converter entre{1, 2, 0, 1, 0}
<-->{C, A, E, B, D}
, que precisa de O (n ^ 2).) Se "nosso estilo" e "estilo comum" não são conversíveis, eles são de fato duas coisas distintas, não são? Obrigado xEncontrei um algoritmo O (n), aqui está uma breve explicação http://antoinecomeau.blogspot.ca/2014/07/mapping-between-permutations-and.html
fonte
A complexidade pode ser reduzida a n * log (n), consulte a seção 10.1.1 ("O código Lehmer (tabela de inversão)", p.232ff) do fxtbook: http://www.jjj.de/fxt/ #fxtbook pule para a seção 10.1.1.1 ("Computação com matrizes grandes" p.235) para obter o método rápido. O código (GPLed, C ++) está na mesma página da web.
fonte
Problema resolvido. No entanto, não tenho certeza se você ainda precisa da solução após esses anos. LOL, acabei de entrar neste site, então ... Verifique minha classe de permutação Java. Você pode se basear em um índice para obter uma permutação de símbolo ou fornecer uma permutação de símbolo e obter o índice.
Aqui está minha aula de pré-mutação
e aqui está minha aula principal para mostrar como usar a aula.
Diverta-se. :)
fonte
Cada elemento pode estar em uma das sete posições. Para descrever a posição de um elemento, você precisaria de três bits. Isso significa que você pode armazenar a posição de todos os elementos em um valor de 32 bits. Isso está longe de ser eficiente, já que essa representação permitiria até que todos os elementos estivessem na mesma posição, mas acredito que o mascaramento de bits deve ser razoavelmente rápido.
No entanto, com mais de 8 posições, você precisará de algo mais bacana.
fonte
Essa é uma função integrada em J :
fonte
Você pode codificar permutações usando um algoritmo recursivo. Se uma N-permutação (alguma ordem dos números {0, .., N-1}) é da forma {x, ...} então codifique-a como x + N * a codificação do (N-1) -permutação representada por "..." nos números {0, N-1} - {x}. Parece um bocado, aqui está um código:
Este algoritmo é O (n ^ 2). Pontos de bônus se alguém tiver um algoritmo O (n).
fonte
Que pergunta interessante!
Se todos os seus elementos forem números, você pode querer considerar convertê-los de strings em números reais. Então, você seria capaz de classificar todas as permutações, colocando-as em ordem e colocando-as em uma matriz. Depois disso, você estará aberto a qualquer um dos vários algoritmos de pesquisa que existem.
fonte
Fui precipitado na minha resposta anterior (excluída), mas tenho a resposta real. É fornecido por um conceito semelhante, o fatorádico , e está relacionado a permutações (minha resposta relacionada a combinações, peço desculpas por essa confusão). Eu odeio apenas postar links da Wikipedia, mas eu escrevi que fiz há algum tempo é ininteligível por algum motivo. Então, posso expandir isso mais tarde, se solicitado.
fonte
Existe um livro escrito sobre isso. Desculpe, mas não me lembro o nome dele (provavelmente você vai encontrá-lo na wikipedia). mas de qualquer maneira eu escrevi uma implementação python desse sistema de enumeração: http://kks.cabal.fi/Kombinaattori Parte dele está em finlandês, mas apenas copie o código e as variáveis de nome ...
fonte
Eu tinha exatamente essa pergunta e pensei em fornecer minha solução Python. É O (n ^ 2).
É muito simples; depois de gerar a representação fatorádica do número, eu apenas escolho e removo os caracteres da string. Excluir da string é o motivo pelo qual esta é uma solução O (n ^ 2).
A solução de Antoine é melhor para desempenho.
fonte
Uma questão relacionada é calcular a permutação inversa, uma permutação que restaurará os vetores permutados à ordem original quando apenas a matriz de permutação for conhecida. Aqui está o código O (n) (em PHP):
David Spector Springtime Software
fonte