Counting inversions in an array

前端 未结 30 1939
死守一世寂寞
死守一世寂寞 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:42

    Note that the answer by Geoffrey Irving is wrong.

    The number of inversions in an array is half the total distance elements must be moved in order to sort the array. Therefore, it can be computed by sorting the array, maintaining the resulting permutation p[i], and then computing the sum of abs(p[i]-i)/2. This takes O(n log n) time, which is optimal.

    An alternative method is given at http://mathworld.wolfram.com/PermutationInversion.html. This method is equivalent to the sum of max(0, p[i]-i), which is equal to the sum of abs(p[i]-i])/2 since the total distance elements move left is equal to the total distance elements move to the right.

    Take the sequence { 3, 2, 1 } as an example. There are three inversions: (3, 2), (3, 1), (2, 1), so the inversion number is 3. However, according to the quoted method the answer would have been 2.

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

    Most answers are based on MergeSort but it isn't the only way to solve this is in O(nlogn)

    I'll discuss a few approaches.

    1. Use a Balanced Binary Search Tree

      • Augment your tree to store frequencies for duplicate elements.
      • The idea is to keep counting greater nodes when the tree is traversed from root to a leaf for insertion.

    Something like this.

    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. Use a Binary Indexed Tree
      • Create a summation BIT.
      • Loop from the end and start finding the count of greater elements.
    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;
    }
    
    1. Use a Segment Tree
      • Create a summation segment Tree.
      • Loop from the end of the array and query between [0, a[i]-1] and update 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;
    }
    

    Also, when using BIT or Segment-Tree a good idea is to do 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;
    }
    
    
    0 讨论(0)
  • 2020-11-22 04:42

    Here is O(n*log(n)) perl implementation:

    sub sort_and_count {
        my ($arr, $n) = @_;
        return ($arr, 0) unless $n > 1;
    
        my $mid = $n % 2 == 1 ? ($n-1)/2 : $n/2;
        my @left = @$arr[0..$mid-1];
        my @right = @$arr[$mid..$n-1];
    
        my ($sleft, $x) = sort_and_count( \@left, $mid );
        my ($sright, $y) = sort_and_count( \@right, $n-$mid);
        my ($merged, $z) = merge_and_countsplitinv( $sleft, $sright, $n );
    
        return ($merged, $x+$y+$z);
    }
    
    sub merge_and_countsplitinv {
        my ($left, $right, $n) = @_;
    
        my ($l_c, $r_c) = ($#$left+1, $#$right+1);
        my ($i, $j) = (0, 0);
        my @merged;
        my $inv = 0;
    
        for my $k (0..$n-1) {
            if ($i<$l_c && $j<$r_c) {
                if ( $left->[$i] < $right->[$j]) {
                    push @merged, $left->[$i];
                    $i+=1;
                } else {
                    push @merged, $right->[$j];
                    $j+=1;
                    $inv += $l_c - $i;
                }
            } else {
                if ($i>=$l_c) {
                    push @merged, @$right[ $j..$#$right ];
                } else {
                    push @merged, @$left[ $i..$#$left ];
                }
                last;
            }
        }
    
        return (\@merged, $inv);
    }
    
    0 讨论(0)
  • 2020-11-22 04:42

    My answer in Python:

    1- Sort the Array first and make a copy of it. In my program, B represents the sorted array. 2- Iterate over the original array (unsorted), and find the index of that element on the sorted list. Also note down the index of the element. 3- Make sure the element doesn't have any duplicates, if it has then you need to change the value of your index by -1. The while condition in my program is exactly doing that. 4- Keep counting the inversion that will your index value, and remove the element once you have calculated its inversion.

    def binarySearch(alist, item):
        first = 0
        last = len(alist) - 1
        found = False
    
        while first <= last and not found:
            midpoint = (first + last)//2
            if alist[midpoint] == item:
                return midpoint
            else:
                if item < alist[midpoint]:
                    last = midpoint - 1
                else:
                    first = midpoint + 1
    
    def solution(A):
    
        B = list(A)
        B.sort()
        inversion_count = 0
        for i in range(len(A)):
            j = binarySearch(B, A[i])
            while B[j] == B[j - 1]:
                if j < 1:
                    break
                j -= 1
    
            inversion_count += j
            B.pop(j)
    
        if inversion_count > 1000000000:
            return -1
        else:
            return inversion_count
    
    print solution([4, 10, 11, 1, 3, 9, 10])
    
    0 讨论(0)
  • 2020-11-22 04:44

    Best optimized way will be to solve it through merge sort where will merging itself we can check how many inversions are required by comparing left and right array. Whenever element at left array is greater than element at right array, it will be inversion.

    Merge sort Approach :-

    Here is the code . Code is exact same as merge sort except code snippet under mergeToParent method where i am counting the inversion under else condition of (left[leftunPicked] < right[rightunPicked])

    public class TestInversionThruMergeSort {
        
        static int count =0;
    
        public static void main(String[] args) {
            int[] arr = {6, 9, 1, 14, 8, 12, 3, 2};
            
    
            partition(arr);
    
            for (int i = 0; i < arr.length; i++) {
    
                System.out.println(arr[i]);
            }
            
            System.out.println("inversions are "+count);
    
        }
    
        public static void partition(int[] arr) {
    
            if (arr.length > 1) {
    
                int mid = (arr.length) / 2;
                int[] left = null;
    
                if (mid > 0) {
                    left = new int[mid];
    
                    for (int i = 0; i < mid; i++) {
                        left[i] = arr[i];
                    }
                }
    
                int[] right = new int[arr.length - left.length];
    
                if ((arr.length - left.length) > 0) {
                    int j = 0;
                    for (int i = mid; i < arr.length; i++) {
                        right[j] = arr[i];
                        ++j;
                    }
                }
    
                partition(left);
                partition(right);
                mergeToParent(left, right, arr);
            }
    
        }
    
        public static void mergeToParent(int[] left, int[] right, int[] parent) {
    
            int leftunPicked = 0;
            int rightunPicked = 0;
            int parentIndex = -1;
    
            while (rightunPicked < right.length && leftunPicked < left.length) {
    
                if (left[leftunPicked] < right[rightunPicked]) {
                    parent[++parentIndex] = left[leftunPicked];
                    ++leftunPicked;
    
                } else {
                    count = count + left.length-leftunPicked;
                    if ((rightunPicked < right.length)) {
                        parent[++parentIndex] = right[rightunPicked];
                        ++rightunPicked;
                    }
                }
    
            }
    
            while (leftunPicked < left.length) {
                parent[++parentIndex] = left[leftunPicked];
                ++leftunPicked;
            }
    
            while (rightunPicked < right.length) {
                parent[++parentIndex] = right[rightunPicked];
                ++rightunPicked;
            }
    
        }
    
    }
    

    Another approach where we can compare the input array with sorted array:- This implementation of Diablo answer. Though this should not be preferred approach as removing the n elements from an array or list is log(n^2).

    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.Iterator;
    import java.util.List;
    
    
    public class TestInversion {
    
        public static void main(String[] args) {
            
            Integer [] arr1 = {6, 9, 1, 14, 8, 12, 3, 2};
            
            List<Integer> arr = new ArrayList(Arrays.asList(arr1));
            List<Integer> sortArr = new ArrayList<Integer>();
            
            for(int i=0;i<arr.size();i++){
                sortArr.add(arr.get(i));
             
            }
            
                
            Collections.sort(sortArr);
            
            int inversion = 0;
            
            Iterator<Integer> iter = arr.iterator();
            
            while(iter.hasNext()){
                
                Integer el = (Integer)iter.next();
                int index = sortArr.indexOf(el);
                
                if(index+1 > 1){
                    inversion = inversion + ((index+1)-1);
                }
                
                //iter.remove();
                sortArr.remove(el);
                
            }
            
            System.out.println("Inversions are "+inversion);
            
            
            
    
        }
    
    
    }
    
    0 讨论(0)
  • 2020-11-22 04:46

    I had a question similar to this for homework actually. I was restricted that it must have O(nlogn) efficiency.

    I used the idea you proposed of using Mergesort, since it is already of the correct efficiency. I just inserted some code into the merging function that was basically: Whenever a number from the array on the right is being added to the output array, I add to the total number of inversions, the amount of numbers remaining in the left array.

    This makes a lot of sense to me now that I've thought about it enough. Your counting how many times there is a greater number coming before any numbers.

    hth.

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