Então aqui está a solução O (n log n) em 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 é um tipo de mesclagem quase normal, toda a magia está oculta na função de mesclagem. Observe que, enquanto o algoritmo de classificação remove as inversões. Enquanto o algoritmo de fusão conta o número de inversões removidas (classificadas, pode-se dizer).
O único momento em que as inversões são removidas é quando o algoritmo pega o elemento do lado direito de uma matriz e o mescla com a matriz principal. O número de inversões removidas por esta operação é o número de elementos restantes do array esquerdo a serem mesclados. :)
Espero que seja suficientemente explicativo.
left.length - i
ao contador de inversão? Eu acho que faria sentido apenas adicionar 1, uma vez que você caiu no caso lógico em que a comparação entre os dois subarrays tem um elemento de array esquerdo maior do que o direito. Alguém pode me explicar como se eu tivesse 5 anos?arr
. Mas não é uma inversão. Você encontrou inversões para todos os elementos na matriz esquerda que são maiores que 6. Em nosso caso, também inclui 8. Portanto, 2 é adicionado acount
, que é igual aleft.length - i
.Eu o encontrei em tempo O (n * log n) pelo seguinte método.
Pegue A [1] e encontre sua posição na matriz classificada B por meio de uma pesquisa binária. O número de inversões para este elemento será um a menos que o número índice de sua posição em B, pois cada número inferior que aparecer após o primeiro elemento de A será uma inversão.
2a. acumule o número de inversões para contrariar a variável num_inversions.
2b. remova A [1] da matriz A e também de sua posição correspondente na matriz B
Aqui está um exemplo de execução desse algoritmo. Matriz original A = (6, 9, 1, 14, 8, 12, 3, 2)
1: Mesclar classificar e copiar para a matriz B
B = (1, 2, 3, 6, 8, 9, 12, 14)
2: Pegue A [1] e pesquisa binária para encontrá-lo na matriz B
A [1] = 6
B = (1, 2, 3, 6 , 8, 9, 12, 14)
6 está na 4ª posição da matriz B, portanto, há 3 inversões. Sabemos disso porque 6 estava na primeira posição na matriz A, portanto, qualquer elemento de valor inferior que apareça posteriormente na matriz A teria um índice de j> i (já que i neste caso é 1).
2.b: Remova A [1] da matriz A e também de sua posição correspondente na matriz B (os elementos em negrito são removidos).
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: Execute novamente a partir da etapa 2 nos novos arrays A e B.
A [1] = 9
B = (1, 2, 3, 8, 9, 12, 14)
9 está agora na 5ª posição da matriz B, portanto, há 4 inversões. Sabemos disso porque 9 estava na primeira posição na matriz A, portanto, qualquer elemento de valor inferior que apareça subsequentemente teria um índice de j> i (já que i, nesse caso, é novamente 1). Remova A [1] da matriz A e também de sua posição correspondente na matriz B (os elementos em negrito são removidos)
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 nessa linha nos dará o número total de inversões para a matriz A assim que o loop for concluído.
A etapa 1 (classificação por mesclagem) levaria O (n * log n) para ser executada. A etapa 2 seria executada n vezes e, a cada execução, faria uma pesquisa binária que leva O (log n) para um total de O (n * log n). O tempo total de execução seria, portanto, O (n * log n) + O (n * log n) = O (n * log n).
Obrigado pela ajuda. Escrever as matrizes de amostra em um pedaço de papel realmente ajudou a visualizar o problema.
fonte
Em Python
fonte
Eu me pergunto por que ninguém mencionou árvores indexadas por binários ainda. Você pode usar um para manter somas de prefixo nos valores de seus elementos de permutação. Então você pode simplesmente prosseguir da direita para a esquerda e contar para cada elemento o número de elementos menores do que à direita:
A complexidade é O (n log n) e o fator constante é muito baixo.
fonte
i -= i & -i
linha? E da mesma formai += i & -i
timeit
compara todas as respostas do Python a essa pergunta, portanto, inclui seu código. Você pode estar interessado em ver os resultados do tempo.Eu tinha uma pergunta semelhante a esta para dever de casa, na verdade. Eu estava restrito que deveria ter eficiência O (nlogn).
Usei a ideia que você propôs de usar o Mergesort, pois já tem a eficiência correta. Acabei de inserir um código na função de mesclagem que era basicamente: Sempre que um número da matriz à direita está sendo adicionado à matriz de saída, adiciono ao número total de inversões, a quantidade de números restantes na matriz esquerda.
Isso faz muito sentido para mim, agora que pensei o suficiente. Você está contando quantas vezes há um número maior vindo antes de qualquer número.
hth.
fonte
O objetivo principal desta resposta é comparar as velocidades das várias versões do Python encontradas aqui, mas também tenho algumas contribuições minhas. (FWIW, acabei de descobrir esta questão enquanto realizava uma pesquisa duplicada).
As velocidades relativas de execução dos algoritmos implementados em CPython podem ser diferentes do que se esperaria de uma análise simples dos algoritmos e da experiência com outras linguagens. Isso porque Python fornece muitas funções e métodos poderosos implementados em C que podem operar em listas e outras coleções com velocidade próxima à que se obteria em uma linguagem totalmente compilada, de modo que essas operações são executadas muito mais rápido do que algoritmos equivalentes implementados "manualmente" com Python código.
O código que tira proveito dessas ferramentas pode frequentemente superar algoritmos teoricamente superiores que tentam fazer tudo com operações Python em itens individuais da coleção. É claro que a quantidade real de dados sendo processados também tem um impacto sobre isso. Mas, para quantidades moderadas de dados, o código que usa um algoritmo O (n²) em execução na velocidade C pode facilmente vencer um algoritmo O (n log n) que faz a maior parte de seu trabalho com operações individuais do Python.
Muitas das respostas postadas para esta questão de contagem de inversão usam um algoritmo baseado em mergesort. Teoricamente, essa é uma boa abordagem, a menos que o tamanho do array seja muito pequeno. Mas o TimSort integrado do Python (um algoritmo de classificação estável híbrido, derivado da classificação por mesclagem e classificação por inserção) é executado na velocidade C, e um mergesort codificado manualmente em Python não pode competir com ele em velocidade.
Uma das soluções mais intrigantes aqui, na resposta postada por Niklas B , usa a classificação embutida para determinar a classificação dos itens da matriz e uma árvore indexada binária (também conhecida como árvore de Fenwick) para armazenar as somas cumulativas necessárias para calcular a inversão contagem. No processo de tentar entender essa estrutura de dados e o algoritmo de Niklas, escrevi algumas variações minhas (postadas abaixo). Mas também descobri que, para tamanhos de lista moderados, é realmente mais rápido usar a
sum
função incorporada do Python do que a adorável árvore Fenwick.Eventualmente, quando o tamanho da lista chega a cerca de 500, o aspecto O (n²) da chamada
sum
dentro dessefor
loop aparece e o desempenho começa a despencar.Mergesort não é o único tipo O (nlogn) e vários outros podem ser utilizados para realizar a contagem de inversão. A resposta de prasadvk usa uma espécie de árvore binária, porém seu código parece estar em C ++ ou um de seus derivados. Então, adicionei uma versão Python. Eu usei originalmente uma classe para implementar os nós da árvore, mas descobri que um dict é visivelmente mais rápido. Acabei usando list, que é ainda mais rápido, embora torne o código um pouco menos legível.
Um bônus do treesort é que é muito mais fácil de implementar iterativamente do que o mergesort. Python não otimiza a recursão e tem um limite de profundidade de recursão (embora isso possa ser aumentado se você realmente precisar). E, claro, as chamadas de função Python são relativamente lentas, então quando você está tentando otimizar para velocidade, é bom evitar chamadas de função, quando prático.
Outro tipo O (nlogn) é o venerável tipo raiz. A grande vantagem é que ele não compara as chaves entre si. A desvantagem é que funciona melhor em sequências contíguas de inteiros, idealmente uma permutação de inteiros em
range(b**m)
queb
geralmente é 2. Eu adicionei algumas versões com base na classificação raiz depois de tentar ler Inversões de contagem, Contagem de intervalo ortogonal offline e problemas relacionados que é ligados no cálculo do número de “inversões” em uma permutação .Para usar a classificação de raiz efetivamente para contar inversões em uma sequência geral
seq
de comprimento n, podemos criar uma permutação derange(n)
que tem o mesmo número de inversões queseq
. Podemos fazer isso em (na pior das hipóteses) tempo O (nlogn) via TimSort. O truque é permutar os índices deseq
por classificaçãoseq
. É mais fácil explicar isso com um pequeno exemplo.resultado
Ao classificar os pares (valor, índice) de
seq
, permutamos os índices deseq
com o mesmo número de trocas que são necessários para colocarseq
em sua ordem original a partir de sua ordem de classificação. Podemos criar essa permutação classificandorange(n)
com uma função chave adequada:resultado
Podemos evitar isso
lambda
usandoseq
o.__getitem__
método de:Isso é apenas um pouco mais rápido, mas estamos procurando todas as melhorias de velocidade que pudermos obter. ;)
O código a seguir executa
timeit
testes em todos os algoritmos Python existentes nesta página, além de alguns dos meus: algumas versões O (n²) de força bruta, algumas variações no algoritmo de Niklas B e, claro, um baseado em mergesort (que escrevi sem me referir às respostas existentes). Ele também tem meu código de treesort baseado em lista derivado aproximadamente do código de prasadvk, e várias funções baseadas em radix sort, algumas usando uma estratégia semelhante às abordagens mergesort, e algumas usandosum
ou uma árvore de Fenwick.Este programa mede o tempo de execução de cada função em uma série de listas aleatórias de inteiros; ele também pode verificar se cada função dá os mesmos resultados que as outras e se não modifica a lista de entrada.
Cada
timeit
chamada fornece um vetor contendo 3 resultados, que classifico. O principal valor a ser observado aqui é o mínimo, os outros valores apenas fornecem uma indicação de quão confiável é esse valor mínimo, conforme discutido na Nota nos documentos dotimeit
módulo .Infelizmente, a saída deste programa é muito grande para incluir nesta resposta, então estou postando em sua própria resposta (wiki da comunidade) .
A saída é de 3 execuções em minha antiga máquina de 2 GHz de núcleo único de 32 bits executando Python 3.6.0 em uma antiga distro derivada do Debian. YMMV. Durante os testes, desliguei meu navegador da Web e desconectei do roteador para minimizar o impacto de outras tarefas na CPU.
A primeira execução testa todas as funções com tamanhos de lista de 5 a 320, com tamanhos de loop de 4096 a 64 (como o tamanho da lista dobra, o tamanho do loop é reduzido pela metade). O conjunto aleatório usado para construir cada lista tem metade do tamanho da própria lista, portanto, é provável que tenhamos muitas duplicatas. Alguns dos algoritmos de contagem de inversão são mais sensíveis a duplicatas do que outros.
A segunda execução usa listas maiores: 640 a 10240 e um tamanho de loop fixo de 8. Para economizar tempo, ele elimina várias das funções mais lentas dos testes. My-força bruta O (n²) funções são apenas maneira muito lenta para estes tamanhos, e como mencionado anteriormente, o meu código que usa
sum
, o que faz tão bem em pequenas e listas moderadas, simplesmente não pode manter-se em grandes listas.A execução final cobre tamanhos de lista de 20480 a 655360 e um tamanho de loop fixo de 4, com as 8 funções mais rápidas. Para tamanhos de lista abaixo de 40.000 ou mais, o código de Tim Babych é o vencedor claro. Muito bem, Tim! O código de Niklas B também tem um bom desempenho geral, embora seja superado nas listas menores. O código baseado em bissecção de "python" também se sai muito bem, embora pareça ser um pouco mais lento com listas enormes com muitas duplicatas, provavelmente devido ao
while
loop linear que usa para passar por cima dos ingênuos.No entanto, para tamanhos de lista muito grandes, os algoritmos baseados em bissecção não podem competir com os verdadeiros algoritmos O (nlogn).
Por favor, veja aqui o resultado
fonte
bisect
é C? Tenho certeza que é Python.O número de inversões pode ser encontrado analisando o processo de mesclagem na classificação de mesclagem:
Ao copiar um elemento da segunda matriz para a matriz de mesclagem (o 9 neste exemplo), ele mantém seu lugar em relação aos outros elementos. Ao copiar um elemento da primeira matriz para a matriz de mesclagem (o 5 aqui), ele é invertido com todos os elementos permanecendo na segunda matriz (2 inversões com o 3 e o 4). Portanto, uma pequena modificação no merge sort pode resolver o problema em O (n ln n).
Por exemplo, apenas descomente as duas # linhas no código mergesort Python abaixo para ter a contagem.
EDIT 1
A mesma tarefa pode ser realizada com uma versão estável de classificação rápida, conhecida por ser um pouco mais rápida:
Escolhendo o pivô como o último elemento, as inversões são bem contadas e o tempo de execução 40% melhor do que a fusão acima.
EDITAR 2
Para desempenho em python, uma versão numpy e numba:
Primeiro, a parte numpy, que usa argsort O (n ln n):
E a parte numba para a abordagem BIT eficiente :
fonte
timeit
compara todas as respostas do Python a essa pergunta, portanto, inclui seu código. Você pode estar interessado em ver os resultados do tempo.timeit
coleção.Observe que a resposta de Geoffrey Irving está errada.
Tome a sequência {3, 2, 1} como exemplo. Existem três inversões: (3, 2), (3, 1), (2, 1), então o número da inversão é 3. No entanto, de acordo com o método citado, a resposta teria sido 2.
fonte
Verifique isso: http://www.cs.jhu.edu/~xfliu/600.363_F03/hw_solution/solution1.pdf
Espero que você tenha a resposta certa.
fonte
Aqui está uma solução possível com variação da árvore binária. Ele adiciona um campo denominado rightSubTreeSize a cada nó da árvore. Continue inserindo números na árvore binária na ordem em que aparecem no array. Se o número for lhs de nó, a contagem de inversão para esse elemento seria (1 + rightSubTreeSize). Uma vez que todos esses elementos são maiores do que o elemento atual, eles teriam aparecido anteriormente na matriz. Se o elemento for para o rhs de um nó, apenas aumente seu rightSubTreeSize. A seguir está o código.
fonte
if(p->data < q->data)
outra forma, as duplicatas não são tratadas corretamente. E não há necessidade de testarq
no topo do loop, umwhile
loop incondicional funciona bem. Além disso, você se esqueceu de mencionar que idioma é esse. :) E sua função parece ter perdido a linha de cabeçalho.fonte
Como essa é uma pergunta antiga, darei minha resposta em C.
fonte
Aqui está a solução c ++
fonte
Aqui está um código C para inversões de contagem
Uma explicação foi dada em detalhes aqui: http://www.geeksforgeeks.org/counting-inversions/
fonte
O (n log n) tempo, solução O (n) espaço em java.
Um mergesort, com um ajuste para preservar o número de inversões realizadas durante a etapa de fusão. (para um mergesort bem explicado, dê uma olhada em http://www.vogella.com/tutorials/JavaAlgorithmsMergesort/article.html )
Uma vez que mergesort pode ser feito no local, a complexidade do espaço pode ser melhorada para O (1).
Ao usar essa classificação, as inversões acontecem apenas na etapa de mesclagem e apenas quando temos que colocar um elemento da segunda parte antes dos elementos da primeira metade, por exemplo
fundido com
temos 3 + 2 + 0 = 5 inversões:
Depois de fazermos as 5 inversões, nossa nova lista mesclada é 0, 1, 5, 6, 10, 15, 22
Há uma tarefa de demonstração no Codility chamada ArrayInversionCount, onde você pode testar sua solução.
fonte
Aqui está a implementação de O (n * log (n)) perl:
fonte
Minha resposta em Python:
1- Classifique o Array primeiro e faça uma cópia dele. Em meu programa, B representa a matriz classificada. 2- Itere sobre o array original (não classificado) e encontre o índice desse elemento na lista classificada. Anote também o índice do elemento. 3- Certifique-se de que o elemento não tem duplicatas, se tiver, então você precisa alterar o valor do seu índice por -1. A condição while em meu programa está fazendo exatamente isso. 4- Continue contando a inversão que será o seu valor de índice e remova o elemento depois de calcular sua inversão.
fonte
timeit
compara todas as respostas do Python a essa pergunta, portanto, inclui seu código. Você pode estar interessado em ver os resultados do tempo.Bem, eu tenho uma solução diferente, mas temo que funcione apenas para elementos de matriz distintos.
Para explicar meu código, continuamos adicionando elementos do final de Array. Para qualquer elemento de matriz de entrada, encontramos o índice do primeiro elemento no vetor v, que é maior do que nosso elemento de entrada, e atribuímos esse valor à contagem de inversão do índice do elemento de entrada .Depois disso, inserimos aquele elemento no vetor v em sua posição correta de forma que o vetor v permaneça em ordem de classificação.
fonte
Outra solução Python, curta. Faz uso do módulo bisect integrado, que fornece funções para inserir o elemento em seu lugar na matriz classificada e para encontrar o índice do elemento na matriz classificada.
A ideia é armazenar os elementos à esquerda do n-ésimo nessa matriz, o que nos permitiria encontrar facilmente o número deles maior que n-ésimo.
fonte
timeit
compara todas as respostas do Python a essa pergunta, portanto, inclui seu código. Você pode estar interessado em ver os resultados do tempo. : DEsta resposta contém os resultados dos
timeit
testes produzidos pelo código em minha resposta principal . Por favor, veja essa resposta para detalhes!fonte
A resposta fácil O (n ^ 2) é usar loops for aninhados e incrementar um contador para cada inversão
Agora, suponho que você queira uma solução mais eficiente, vou pensar sobre isso.
fonte
Uma solução possível em C ++ que satisfaça o requisito de complexidade de tempo O (N * log (N)) seria a seguinte.
Ele difere de uma classificação de mesclagem regular apenas pelo contador.
fonte
Aqui está minha solução O (n log n) em Ruby:
E alguns casos de teste:
fonte
A melhor maneira otimizada será resolvê-lo por meio de merge sort, onde mesclar-se, podemos verificar quantas inversões são necessárias comparando o array esquerdo e direito. Sempre que o elemento na matriz esquerda for maior do que o elemento na matriz direita, haverá inversão.
Abordagem de mesclagem de classificação: -
Aqui está o código. O código é exatamente o mesmo que a classificação por mesclagem, exceto o trecho de código sob o
mergeToParent
método onde estou contando a inversão sob outra condição de(left[leftunPicked] < right[rightunPicked])
Outra abordagem onde podemos comparar a matriz de entrada com a matriz classificada: - Esta implementação da resposta do Diablo. Embora essa não seja a abordagem preferida, pois a remoção dos n elementos de uma matriz ou lista é log (n ^ 2).
fonte
O número máximo de inversões possíveis para uma lista de tamanho
n
pode ser generalizado por uma expressão:Portanto, para uma matriz de tamanho
6
máximo, as inversões possíveis serão iguais15
.Para alcançar uma complexidade de
n logn
, poderíamos adicionar o algoritmo de inversão na classificação por mesclagem.Aqui estão as etapas generalizadas:
inversionCount += leftSubArray.length
É isso aí!
Este é um exemplo simples que fiz usando Javascript:
fonte
Implementação de inversões de contagem em uma matriz com classificação por mesclagem em Swift:
Observe que o número de trocas é incrementado em
(que é o comprimento relativo do lado esquerdo da matriz menos o índice do elemento atual no lado esquerdo)
... porque esse é o número de elementos que o elemento no lado direito da matriz teve que pular (número de inversões) para ser classificado.
fonte
A maioria das respostas é baseada em,
MergeSort
mas não é a única maneira de resolver isso é emO(nlogn)
Vou discutir algumas abordagens.
Use um
Balanced Binary Search Tree
Algo assim.
Binary Indexed Tree
Segment Tree
[0, a[i]-1]
e atualizara[i] with 1
Além disso, ao usar
BIT
ouSegment-Tree
uma boa ideia é fazerCoordinate compression
fonte
C ++ Θ (n lg n) Solução com a impressão dos pares que se constituem na contagem de inversões.
fonte
Use mergesort, em merge step incremeant counter se o número copiado para a saída for da matriz direita.
fonte
Recentemente, tive que fazer isso em R:
fonte