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.

2voto

Dheeraj Sachan Points 21

Voici la solution c++

/**
*array sorting needed to verify if first arrays n'th element is greater than sencond arrays
*some element then all elements following n will do the same
*/
#include<stdio.h>
#include<iostream>
using namespace std;
int countInversions(int array[],int size);
int merge(int arr1[],int size1,int arr2[],int size2,int[]);
int main()
{
    int array[] = {2, 4, 1, 3, 5};
    int size = sizeof(array) / sizeof(array[0]);
    int x = countInversions(array,size);
    printf("number of inversions = %d",x);
}

int countInversions(int array[],int size)
{
    if(size > 1 )
    {
    int mid = size / 2;
    int count1 = countInversions(array,mid);
    int count2 = countInversions(array+mid,size-mid);
    int temp[size];
    int count3 = merge(array,mid,array+mid,size-mid,temp);
    for(int x =0;x<size ;x++)
    {
        array[x] = temp[x];
    }
    return count1 + count2 + count3;
    }else{
        return 0;
    }
}

int merge(int arr1[],int size1,int arr2[],int size2,int temp[])
{
    int count  = 0;
    int a = 0;
    int b = 0;
    int c = 0;
    while(a < size1 && b < size2)
    {
        if(arr1[a] < arr2[b])
        {
            temp[c] = arr1[a];
            c++;
            a++;
        }else{
            temp[c] = arr2[b];
            b++;
            c++;
            count = count + size1 -a;
        }
    }

    while(a < size1)
    {
        temp[c] = arr1[a];
        c++;a++;
    }

while(b < size2)
    {
        temp[c] = arr2[b];
        c++;b++;
    }

    return count;
}

2voto

Ankit Sharma Points 59

La plupart des réponses sont basées sur MergeSort mais ce n'est pas le cas, la seule façon de résoudre ce problème est dans O(nlogn)

Je vais discuter de quelques approches.

  1. Utilisez un Balanced Binary Search Tree

    • Augmentez votre arbre pour stocker les fréquences des éléments dupliqués.
    • L'idée est de continuer à compter les nœuds plus grands lorsque l'arbre est traversé de la racine à une feuille pour l'insertion.

Quelque chose comme ça.

Node *insert(Node* root, int data, int& count){
    if(!root) return new Node(data);
    if(root->data == data){
        root->freq++;
        count += getSize(root->right);
    }
    else if(root->data > data){
        count += getSize(root->right) + root->freq;
        root->left = insert(root->left, data, count);
    }
    else root->right = insert(root->right, data, count);
    return balance(root);
}

int getCount(int *a, int n){
    int c = 0;
    Node *root = NULL;
    for(auto i=0; i<n; i++) root = insert(root, a[i], c);
    return c;
}
  1. Utilisez un Binary Indexed Tree

    • Créez un TBI de sommation.
    • Bouclez à partir de la fin et commencez à trouver le nombre d'éléments plus grands.

    int getInversions(int[] a) { int n = a.length, inversions = 0; int[] bit = new int[n+1]; compress(a); BIT b = new BIT(); for (int i=n-1; i>=0; i--) { inversions += b.getSum(bit, a[i] - 1); b.update(bit, n, a[i], 1); } return inversions; }

  2. Utilisez un Segment Tree

    • Créez un arbre de segments de sommation.
    • Boucle à partir de la fin du tableau et interrogation entre [0, a[i]-1] et de mettre à jour a[i] with 1

    int getInversions(int *a, int n) { int N = n + 1, c = 0; compress(a, n); int tree[N<<1] = {0}; for (int i=n-1; i>=0; i--) { c+= query(tree, N, 0, a[i] - 1); update(tree, N, a[i], 1); } return c; }

De même, lorsque vous utilisez BIT ou Segment-Tree une bonne idée est de faire Coordinate compression

void compress(int *a, int n) {
    int temp[n];
    for (int i=0; i<n; i++) temp[i] = a[i];
    sort(temp, temp+n);
    for (int i=0; i<n; i++) a[i] = lower_bound(temp, temp+n, a[i]) - temp + 1;
}

1voto

mbillard Points 15829

La réponse facile O(n^2) est d'utiliser des boucles for imbriquées et d'incrémenter un compteur pour chaque inversion.

int counter = 0;

for(int i = 0; i < n - 1; i++)
{
    for(int j = i+1; j < n; j++)
    {
        if( A[i] > A[j] )
        {
            counter++;
        }
    }
}

return counter;

Maintenant, je suppose que vous voulez une solution plus efficace, je vais y réfléchir.

1voto

banarun Points 1493

Voici un code C pour les inversions de comptage

#include <stdio.h>
#include <stdlib.h>

int  _mergeSort(int arr[], int temp[], int left, int right);
int merge(int arr[], int temp[], int left, int mid, int right);

/* This function sorts the input array and returns the
   number of inversions in the array */
int mergeSort(int arr[], int array_size)
{
    int *temp = (int *)malloc(sizeof(int)*array_size);
    return _mergeSort(arr, temp, 0, array_size - 1);
}

/* An auxiliary recursive function that sorts the input array and
  returns the number of inversions in the array. */
int _mergeSort(int arr[], int temp[], int left, int right)
{
  int mid, inv_count = 0;
  if (right > left)
  {
    /* Divide the array into two parts and call _mergeSortAndCountInv()
       for each of the parts */
    mid = (right + left)/2;

    /* Inversion count will be sum of inversions in left-part, right-part
      and number of inversions in merging */
    inv_count  = _mergeSort(arr, temp, left, mid);
    inv_count += _mergeSort(arr, temp, mid+1, right);

    /*Merge the two parts*/
    inv_count += merge(arr, temp, left, mid+1, right);
  }
  return inv_count;
}

/* This funt merges two sorted arrays and returns inversion count in
   the arrays.*/
int merge(int arr[], int temp[], int left, int mid, int right)
{
  int i, j, k;
  int inv_count = 0;

  i = left; /* i is index for left subarray*/
  j = mid;  /* i is index for right subarray*/
  k = left; /* i is index for resultant merged subarray*/
  while ((i <= mid - 1) && (j <= right))
  {
    if (arr[i] <= arr[j])
    {
      temp[k++] = arr[i++];
    }
    else
    {
      temp[k++] = arr[j++];

     /*this is tricky -- see above explanation/diagram for merge()*/
      inv_count = inv_count + (mid - i);
    }
  }

  /* Copy the remaining elements of left subarray
   (if there are any) to temp*/
  while (i <= mid - 1)
    temp[k++] = arr[i++];

  /* Copy the remaining elements of right subarray
   (if there are any) to temp*/
  while (j <= right)
    temp[k++] = arr[j++];

  /*Copy back the merged elements to original array*/
  for (i=left; i <= right; i++)
    arr[i] = temp[i];

  return inv_count;
}

/* Driver progra to test above functions */
int main(int argv, char** args)
{
  int arr[] = {1, 20, 6, 4, 5};
  printf(" Number of inversions are %d \n", mergeSort(arr, 5));
  getchar();
  return 0;
}

Une explication a été donnée en détail ici : http://www.geeksforgeeks.org/counting-inversions/

1voto

Andrey Petrov Points 36

Solution en O(n log n) temps, O(n) espace en java.

Un mergesort, avec une modification pour préserver le nombre d'inversions effectuées pendant l'étape de fusion. (pour un mergesort bien expliqué, jetez un oeil à http://www.vogella.com/tutorials/JavaAlgorithmsMergesort/article.html )

Puisque le tri par fusion peut être effectué sur place, la complexité spatiale peut être améliorée à O(1).

Lorsque l'on utilise cette sorte, les inversions ne se produisent que dans l'étape de fusion et seulement lorsque l'on doit placer un élément de la seconde partie avant des éléments de la première moitié, par ex.

  • 0 5 10 15

fusionné avec

  • 1 6 22

nous avons 3 + 2 + 0 = 5 inversions :

  • 1 avec {5, 10, 15}
  • 6 avec {10, 15}
  • 22 avec {}

Après avoir fait les 5 inversions, notre nouvelle liste fusionnée est de 0, 1, 5, 6, 10, 15, 22

Il existe une tâche de démonstration sur Codility appelée ArrayInversionCount, où vous pouvez tester votre solution.

    public class FindInversions {

    public static int solution(int[] input) {
        if (input == null)
            return 0;
        int[] helper = new int[input.length];
        return mergeSort(0, input.length - 1, input, helper);
    }

    public static int mergeSort(int low, int high, int[] input, int[] helper) {
        int inversionCount = 0;
        if (low < high) {
            int medium = low + (high - low) / 2;
            inversionCount += mergeSort(low, medium, input, helper);
            inversionCount += mergeSort(medium + 1, high, input, helper);
            inversionCount += merge(low, medium, high, input, helper);
        }
        return inversionCount;
    }

    public static int merge(int low, int medium, int high, int[] input, int[] helper) {
        int inversionCount = 0;

        for (int i = low; i <= high; i++)
            helper[i] = input[i];

        int i = low;
        int j = medium + 1;
        int k = low;

        while (i <= medium && j <= high) {
            if (helper[i] <= helper[j]) {
                input[k] = helper[i];
                i++;
            } else {
                input[k] = helper[j];
                // the number of elements in the first half which the j element needs to jump over.
                // there is an inversion between each of those elements and j.
                inversionCount += (medium + 1 - i);
                j++;
            }
            k++;
        }

        // finish writing back in the input the elements from the first part
        while (i <= medium) {
            input[k] = helper[i];
            i++;
            k++;
        }
        return inversionCount;
    }

}

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