Counting inversions in an array

前端 未结 30 1936
死守一世寂寞
死守一世寂寞 2020-11-22 04:14

I\'m designing an algorithm to do the following: Given array A[1... n], for every i < j, find all inversion pairs such that A[i] > A[j]

相关标签:
30条回答
  • 2020-11-22 04:51

    O(n log n) time, O(n) space solution in java.

    A mergesort, with a tweak to preserve the number of inversions performed during the merge step. (for a well explained mergesort take a look at http://www.vogella.com/tutorials/JavaAlgorithmsMergesort/article.html )

    Since mergesort can be made in place, the space complexity may be improved to O(1).

    When using this sort, the inversions happen only in the merge step and only when we have to put an element of the second part before elements from the first half, e.g.

    • 0 5 10 15

    merged with

    • 1 6 22

    we have 3 + 2 + 0 = 5 inversions:

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

    After we have made the 5 inversions, our new merged list is 0, 1, 5, 6, 10, 15, 22

    There is a demo task on Codility called ArrayInversionCount, where you can test your 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;
        }
    
    }
    
    0 讨论(0)
  • 2020-11-22 04:51

    Maximum number of inversions possible for a list of size n could be generalized by an expression:

    maxPossibleInversions = (n * (n-1) ) / 2
    

    So for an array of size 6 maximum possible inversions will equal 15.

    To achieve a complexity of n logn we could piggy back the inversion algorithm on merge sort.

    Here are the generalized steps:

    • Split the array into two
    • Call the mergeSort routine. If the element in the left subarray is greater than the element in right sub array make inversionCount += leftSubArray.length

    That's it!

    This is a simple example, I made using Javascript:

    var arr = [6,5,4,3,2,1]; // Sample input array
    
    var inversionCount = 0;
    
    function mergeSort(arr) {
        if(arr.length == 1)
            return arr;
    
        if(arr.length > 1) {
            let breakpoint = Math.ceil((arr.length/2));
            // Left list starts with 0, breakpoint-1
            let leftList = arr.slice(0,breakpoint);
            // Right list starts with breakpoint, length-1
            let rightList = arr.slice(breakpoint,arr.length);
    
            // Make a recursive call
            leftList = mergeSort(leftList);
            rightList = mergeSort(rightList);
    
            var a = merge(leftList,rightList);
            return a;
        }
    }
    
    function merge(leftList,rightList) {
        let result = [];
        while(leftList.length && rightList.length) {
            /**
             * The shift() method removes the first element from an array
             * and returns that element. This method changes the length
             * of the array.
             */
            if(leftList[0] <= rightList[0]) {
                result.push(leftList.shift());
            }else{
                inversionCount += leftList.length;
                result.push(rightList.shift());
            }
        }
    
        while(leftList.length)
            result.push(leftList.shift());
    
        while(rightList.length)
            result.push(rightList.shift());
    
        console.log(result);
        return result;
    }
    
    mergeSort(arr);
    console.log('Number of inversions: ' + inversionCount);
    
    0 讨论(0)
  • 2020-11-22 04:52

    Since this is an old question, I'll provide my answer in C.

    #include <stdio.h>
    
    int count = 0;
    int inversions(int a[], int len);
    void mergesort(int a[], int left, int right);
    void merge(int a[], int left, int mid, int right);
    
    int main() {
      int a[] = { 1, 5, 2, 4, 0 };
      printf("%d\n", inversions(a, 5));
    }
    
    int inversions(int a[], int len) {
      mergesort(a, 0, len - 1);
      return count;
    }
    
    void mergesort(int a[], int left, int right) {
      if (left < right) {
         int mid = (left + right) / 2;
         mergesort(a, left, mid);
         mergesort(a, mid + 1, right);
         merge(a, left, mid, right);
      }
    }
    
    void merge(int a[], int left, int mid, int right) {
      int i = left;
      int j = mid + 1;
      int k = 0;
      int b[right - left + 1];
      while (i <= mid && j <= right) {
         if (a[i] <= a[j]) {
           b[k++] = a[i++];
         } else {
           printf("right element: %d\n", a[j]);
           count += (mid - i + 1);
           printf("new count: %d\n", count);
           b[k++] = a[j++];
         }
      }
      while (i <= mid)
        b[k++] = a[i++];
      while (j <= right)
        b[k++] = a[j++];
      for (i = left, k = 0; i <= right; i++, k++) {
        a[i] = b[k];
      }
    }
    
    0 讨论(0)
  • 2020-11-22 04:52

    One possible solution in C++ satisfying the O(N*log(N)) time complexity requirement would be as follows.

    #include <algorithm>
    
    vector<int> merge(vector<int>left, vector<int>right, int &counter)
    {
    
        vector<int> result;
    
        vector<int>::iterator it_l=left.begin();
        vector<int>::iterator it_r=right.begin();
    
        int index_left=0;
    
        while(it_l!=left.end() || it_r!=right.end())
        {
    
            // the following is true if we are finished with the left vector 
            // OR if the value in the right vector is the smaller one.
    
            if(it_l==left.end() || (it_r!=right.end() && *it_r<*it_l) )
            {
                result.push_back(*it_r);
                it_r++;
    
                // increase inversion counter
                counter+=left.size()-index_left;
            }
            else
            {
                result.push_back(*it_l);
                it_l++;
                index_left++;
    
            }
        }
    
        return result;
    }
    
    vector<int> merge_sort_and_count(vector<int> A, int &counter)
    {
    
        int N=A.size();
        if(N==1)return A;
    
        vector<int> left(A.begin(),A.begin()+N/2);
        vector<int> right(A.begin()+N/2,A.end());
    
        left=merge_sort_and_count(left,counter);
        right=merge_sort_and_count(right,counter);
    
    
        return merge(left, right, counter);
    
    }
    

    It differs from a regular merge sort only by the counter.

    0 讨论(0)
  • 2020-11-22 04:55

    Well I have a different solution but I am afraid that would work only for distinct array elements.

    //Code
    #include <bits/stdc++.h>
    using namespace std;
    
    int main()
    {
        int i,n;
        cin >> n;
        int arr[n],inv[n];
        for(i=0;i<n;i++){
            cin >> arr[i];
        }
        vector<int> v;
        v.push_back(arr[n-1]);
        inv[n-1]=0;
        for(i=n-2;i>=0;i--){
            auto it = lower_bound(v.begin(),v.end(),arr[i]); 
            //calculating least element in vector v which is greater than arr[i]
            inv[i]=it-v.begin();
            //calculating distance from starting of vector
            v.insert(it,arr[i]);
            //inserting that element into vector v
        }
        for(i=0;i<n;i++){
            cout << inv[i] << " ";
        }
        cout << endl;
        return 0;
    }
    

    To explain my code we keep on adding elements from the end of Array.For any incoming array element we find the index of first element in vector v which is greater than our incoming element and assign that value to inversion count of the index of incoming element.After that we insert that element into vector v at it's correct position such that vector v remain in sorted order.

    //INPUT     
    4
    2 1 4 3
    
    //OUTPUT    
    1 0 1 0
    
    //To calculate total inversion count just add up all the elements in output array
    
    0 讨论(0)
  • 2020-11-22 04:57

    I wonder why nobody mentioned binary-indexed trees yet. You can use one to maintain prefix sums on the values of your permutation elements. Then you can just proceed from right to left and count for every element the number of elements smaller than it to the right:

    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
    

    The complexity is O(n log n), and the constant factor is very low.

    0 讨论(0)
提交回复
热议问题