128 votes

Compter les inversions dans un tableau

Je suis en train de concevoir un algorithme pour faire ce qui suit : Étant donné un tableau A[1... n] pour chaque i < j trouver toutes les paires d'inversion telles que A[i] > A[j] . J'utilise le tri par fusion et copie le tableau A dans le tableau B, puis compare les deux tableaux, mais j'ai du mal à voir comment je peux utiliser cette méthode pour trouver le nombre d'inversions. Toute indication ou aide serait grandement appréciée.

153voto

Marek Kirejczyk Points 1313

Voici donc une solution 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);
}

C'est un tri par fusion presque normal, toute la magie est cachée dans la fonction de fusion. Notez que l'algorithme de tri supprime les inversions. Pendant la fusion, l'algorithme compte le nombre d'inversions supprimées (triées, pourrait-on dire).

Le seul moment où les inversions sont supprimées est lorsque l'algorithme prend un élément du côté droit d'un tableau et le fusionne avec le tableau principal. Le nombre d'inversions supprimées par cette opération est le nombre d'éléments restants du tableau de gauche à fusionner :)

J'espère que c'est suffisamment explicatif.

2 votes

J'ai essayé de l'exécuter et je n'ai pas obtenu la bonne réponse. Êtes-vous censé appeler invCount(intArray) dans main pour commencer ? Le tableau intArray étant le tableau non trié de nombres entiers ? Je l'ai exécuté avec un tableau de plusieurs entiers et j'ai obtenu une réponse de -1887062008. Qu'est-ce que je fais de mal ?

0 votes

Voici les résultats des tests : lien

4 votes

+1, Voir solution similaire dans C++11 y compris une solution générale basée sur les itérateurs et un banc d'essai aléatoire utilisant des séquences de 5 à 25 éléments. Profitez-en !

89voto

el diablo Points 1035

Je l'ai trouvé en temps O(n * log n) par la méthode suivante.

  1. Fusionner le tableau de tri A et créer une copie (tableau B)

  2. Prenez A[1] et trouvez sa position dans le tableau trié B par une recherche binaire. Le nombre d'inversions pour cet élément sera inférieur d'une unité au numéro d'index de sa position dans B puisque chaque nombre inférieur qui apparaît après le premier élément de A sera une inversion.

    2a. accumuler le nombre d'inversions dans la variable de compteur num_inversions.

    2b. supprimer A[1] du tableau A et également de sa position correspondante dans le tableau B

  3. reprendre l'étape 2 jusqu'à ce qu'il n'y ait plus d'éléments dans A.

Voici un exemple d'exécution de cet algorithme. Tableau original A = (6, 9, 1, 14, 8, 12, 3, 2)

1 : Fusionner le tri et copier dans le tableau B

B = (1, 2, 3, 6, 8, 9, 12, 14)

2 : Prendre A[1] et faire une recherche binaire pour le trouver dans le tableau B

A[1] = 6

B = (1, 2, 3, 6 , 8, 9, 12, 14)

Le 6 est en 4ème position du tableau B, il y a donc 3 inversions. Nous le savons parce que 6 était en première position dans le tableau A, donc tout élément de valeur inférieure qui apparaîtrait ensuite dans le tableau A aurait un indice de j > i (puisque i dans ce cas est 1).

2.b : Retirer A[1] du tableau A et aussi de sa position correspondante dans le tableau B (les éléments en gras sont retirés).

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 : Ré-exécutez l'étape 2 sur les nouvelles matrices A et B.

A[1] = 9

B = (1, 2, 3, 8, 9, 12, 14)

Le 9 est maintenant en 5ème position du tableau B, il y a donc 4 inversions. Nous le savons parce que 9 était en première position dans le tableau A, donc tout élément de valeur inférieure qui apparaîtrait par la suite aurait un indice de j > i (puisque i dans ce cas est à nouveau 1). Retirez A[1] du tableau A et également de sa position correspondante dans le tableau B (les éléments en gras sont retirés).

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)

En continuant dans cette voie, nous obtiendrons le nombre total d'inversions pour le tableau A une fois la boucle terminée.

L'étape 1 (tri par fusion) s'exécute en O(n * log n). L'étape 2 s'exécuterait n fois et, à chaque exécution, effectuerait une recherche binaire qui prendrait O(log n) pour s'exécuter, soit un total de O(n * log n). Le temps d'exécution total serait donc de O(n * log n) + O(n * log n) = O(n * log n).

Merci pour votre aide. Le fait d'écrire les tableaux d'échantillons sur une feuille de papier m'a vraiment aidé à visualiser le problème.

1 votes

Pourquoi utiliser le tri par fusion et non le tri rapide ?

6 votes

@Alcott Quick sort a le pire temps d'exécution de O(n^2), lorsque la liste est déjà triée, et que le premier pivot est choisi à chaque tour. Le pire cas de Merge sort est O(n log n).

35 votes

L'étape de suppression d'un tableau standard rend votre algorithme O(n^2), en raison du décalage des valeurs. (C'est pourquoi le tri par insertion est O(n^2)).

44voto

Andrew Rollings Points 8361

Le seul conseil que je pourrais donner à ce sujet (qui ressemble étrangement à une question de devoir ;) ) est de le faire d'abord manuellement avec un petit ensemble de nombres (par exemple 5), puis de noter les étapes que vous avez suivies pour résoudre le problème.

Cela devrait vous permettre de trouver une solution générique que vous pourrez utiliser pour écrire le code.

40voto

Niklas B. Points 40619

Je me demande pourquoi personne n'a mentionné arbres à indexation binaire pourtant. Vous pouvez en utiliser un pour maintenir des sommes préfixes sur les valeurs de vos éléments de permutation. Vous pouvez alors procéder de droite à gauche et compter pour chaque élément le nombre d'éléments plus petits que lui à droite :

def count_inversions(a):
  res = 0
  counts = [0]*(len(a)+1)
  rank = { v : i+1 for i, v in enumerate(sorted(a)) }
  for x in reversed(a):
    i = rank[x] - 1
    while i:
      res += counts[i]
      i -= i & -i
    i = rank[x]
    while i <= len(a):
      counts[i] += 1
      i += i & -i
  return res

La complexité est O(n log n), et le facteur constant est très faible.

29voto

mkso Points 1361

En Python

# O(n log n)

def count_inversion(lst):
    return merge_count_inversion(lst)[1]

def merge_count_inversion(lst):
    if len(lst) <= 1:
        return lst, 0
    middle = int( len(lst) / 2 )
    left, a = merge_count_inversion(lst[:middle])
    right, b = merge_count_inversion(lst[middle:])
    result, c = merge_count_split_inversion(left, right)
    return result, (a + b + c)

def merge_count_split_inversion(left, right):
    result = []
    count = 0
    i, j = 0, 0
    left_len = len(left)
    while i < left_len and j < len(right):
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            count += left_len - i
            j += 1
    result += left[i:]
    result += right[j:]
    return result, count        

#test code
input_array_1 = []  #0
input_array_2 = [1] #0
input_array_3 = [1, 5]  #0
input_array_4 = [4, 1] #1
input_array_5 = [4, 1, 2, 3, 9] #3
input_array_6 = [4, 1, 3, 2, 9, 5]  #5
input_array_7 = [4, 1, 3, 2, 9, 1]  #8

print count_inversion(input_array_1)
print count_inversion(input_array_2)
print count_inversion(input_array_3)
print count_inversion(input_array_4)
print count_inversion(input_array_5)
print count_inversion(input_array_6)
print count_inversion(input_array_7)

19 votes

Je suis perplexe quant à la façon dont il est parvenu à atteindre +13 - je ne suis pas particulièrement doué en Python, mais il semble que c'est à peu près la même chose que la version Java présentée 2 ans auparavant sauf que cette ne fournit aucune explication, quelle qu'elle soit . Publier des réponses dans toutes les autres langues est activement nuisible OMI - il y a probablement des milliers de langues, si ce n'est beaucoup plus - j'espère que personne ne soutiendra que nous devrions publier des milliers de réponses à une question -. Stack Exchange n'était pas fait pour ça.

0 votes

@Dukeling Votre point de vue pourrait bien être correct pour ce que j'en sais. Ce qui me fait douter, cependant, c'est que vous avez dû utiliser le sophisme de la fausse alternative pour le justifier. Il y a peut-être des milliers de langages, mais il n'y a pas des milliers de langages extrêmement populaires. Il n'y en a probablement même pas cinq devant Python. Maintenant, vous auriez pu dire "J'espère que personne ne soutiendra que nous devrions afficher cinq réponses à une question"...

2 votes

@tennenrishin Ok, peut-être pas des milliers. Mais où fixer la limite ? Il y en a actuellement, d'après mes calculs, dix réponses donnant la même approche déjà. C'est à peu près 43% des réponses (à l'exclusion de la non-réponse) - c'est beaucoup d'espace à occuper étant donné qu'il y a une demi-douzaine d'autres approches présentées ici. Même s'il n'y a que deux réponses pour la même approche, cela dilue encore inutilement les réponses. Et j'ai fait un argument assez décent pour cette réponse précise n'étant pas utile dans mon commentaire précédent.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X