Подсчет инверсий в массиве - PullRequest
95 голосов
/ 03 декабря 2008

Я разрабатываю алгоритм, который выполняет следующие действия: По заданному массиву A[1... n] для каждого i < j найдите все пары инверсий, такие что A[i] > A[j]. Я использую сортировку слиянием и копирую массив A в массив B, а затем сравниваю два массива, но мне трудно понять, как я могу использовать это, чтобы найти число инверсий. Будем весьма благодарны за любые советы или помощь.

Ответы [ 34 ]

0 голосов
/ 21 октября 2009

Использовать сортировку слиянием в счетчике инкремента шага слияния, если число, скопированное на выход, получено из правого массива.

0 голосов
/ 05 августа 2014

Вот мое решение O (n log n) в Ruby:

def solution(t)
    sorted, inversion_count = sort_inversion_count(t)
    return inversion_count
end

def sort_inversion_count(t)
    midpoint = t.length / 2
    left_half = t[0...midpoint]
    right_half = t[midpoint..t.length]

    if midpoint == 0
        return t, 0
    end

    sorted_left_half, left_half_inversion_count = sort_inversion_count(left_half)
    sorted_right_half, right_half_inversion_count = sort_inversion_count(right_half)

    sorted = []
    inversion_count = 0
    while sorted_left_half.length > 0 or sorted_right_half.length > 0
        if sorted_left_half.empty?
            sorted.push sorted_right_half.shift
        elsif sorted_right_half.empty?
            sorted.push sorted_left_half.shift
        else
            if sorted_left_half[0] > sorted_right_half[0]
                inversion_count += sorted_left_half.length
                sorted.push sorted_right_half.shift
            else
                sorted.push sorted_left_half.shift
            end
        end
    end

    return sorted, inversion_count + left_half_inversion_count + right_half_inversion_count
end

И некоторые тестовые случаи:

require "minitest/autorun"

class TestCodility < Minitest::Test
    def test_given_example
        a = [-1, 6, 3, 4, 7, 4]
        assert_equal solution(a), 4
    end

    def test_empty
        a = []
        assert_equal solution(a), 0
    end

    def test_singleton
        a = [0]
        assert_equal solution(a), 0
    end

    def test_none
        a = [1,2,3,4,5,6,7]
        assert_equal solution(a), 0
    end

    def test_all
        a = [5,4,3,2,1]
        assert_equal solution(a), 10
    end

    def test_clones
        a = [4,4,4,4,4,4]
        assert_equal solution(a), 0
    end
end
0 голосов
/ 06 июля 2014

C-код легко понять:

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

//To print an array
void print(int arr[],int n)
{
    int i;
    for(i=0,printf("\n");i<n;i++)
        printf("%d ",arr[i]);
    printf("\n");
}

//Merge Sort
int merge(int arr[],int left[],int right[],int l,int r)
{
    int i=0,j=0,count=0;
    while(i<l || j<r)
    {
        if(i==l)
        {
            arr[i+j]=right[j];
            j++;
        }
        else if(j==r)
        {
            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+=l-i;
            j++;
        }
    }
    //printf("\ncount:%d\n",count);
    return count;
}

//Inversion Finding
int inversions(int arr[],int high)
{
    if(high<1)
        return 0;

    int mid=(high+1)/2;
    int left[mid];
    int right[high-mid+1];

    int i,j;
    for(i=0;i<mid;i++)
        left[i]=arr[i];


    for(i=high-mid,j=high;j>=mid;i--,j--)
        right[i]=arr[j];

    //print(arr,high+1);
    //print(left,mid);
    //print(right,high-mid+1);

    return inversions(left,mid-1) + inversions(right,high-mid) + merge(arr,left,right,mid,high-mid+1);

}
int main()
{
    int arr[]={6,9,1,14,8,12,3,2};
    int n=sizeof(arr)/sizeof(arr[0]);
    print(arr,n);
    printf("%d ",inversions(arr,n-1));
    return 0;
}
0 голосов
/ 02 августа 2016

Лучший оптимизированный способ - это решить с помощью сортировки слиянием, где при слиянии мы можем проверить, сколько инверсий требуется, сравнивая левый и правый массив. Когда элемент в левом массиве больше, чем элемент в правом массиве, это будет инверсия.

Подход сортировки слиянием: -

Вот код. Код точно такой же, как сортировка слиянием, за исключением фрагмента кода в методе mergeToParent, где я считаю инверсию при условии (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;
        }

    }

}

Другой подход, в котором мы можем сравнить входной массив с отсортированным массивом: - Это реализация Diablo ответа. Хотя это не должно быть предпочтительным подходом, так как удаление n элементов из массива или списка - это 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 голосов
/ 03 декабря 2008

Количество инверсий в массиве составляет половину общего расстояния, которое элементы должны быть перемещены для сортировки массива. Следовательно, его можно вычислить, отсортировав массив, сохранив полученную перестановку p [i], а затем вычислив сумму abs (p [i] -i) / 2. Это занимает O (n log n) времени, что является оптимальным.

Альтернативный метод дан в http://mathworld.wolfram.com/PermutationInversion.html. Этот метод эквивалентен сумме max (0, p [i] -i), которая равна сумме abs (p [i] -i) ]) / 2, так как общее расстояние элементов, перемещающихся влево, равно общему расстоянию элементов, перемещающихся вправо.

РЕДАКТИРОВАТЬ: Этот метод является неправильным (см. Комментарии), и, к сожалению, нет способа исправить это, сохранив символ метода.

0 голосов
/ 28 марта 2017

Максимальное количество возможных инверсий для списка размером n может быть обобщено выражением:

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

Так что для массива размером 6 максимально возможные инверсии будут равны 15.

Для достижения сложности n logn мы могли бы добавить алгоритм инверсии при сортировке слиянием.

Вот обобщенные шаги:

  • Разделить массив на два
  • Вызов процедуры mergeSort. Если элемент в левом подмассиве больше, чем элемент в правом подмассиве, сделать inversionCount += leftSubArray.length

Вот и все!

Это простой пример, который я сделал с использованием 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 голосов
/ 16 сентября 2013

Одним из возможных решений в C ++, удовлетворяющих требованию временной сложности O (N * log (N)), было бы следующее:

#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);

}

Отличается от обычной сортировки слиянием только счетчиком.

0 голосов
/ 26 ноября 2013

Мне недавно пришлось сделать это в R:

inversionNumber <- function(x){
    mergeSort <- function(x){
        if(length(x) == 1){
            inv <- 0
        } else {
            n <- length(x)
            n1 <- ceiling(n/2)
            n2 <- n-n1
            y1 <- mergeSort(x[1:n1])
            y2 <- mergeSort(x[n1+1:n2])
            inv <- y1$inversions + y2$inversions
            x1 <- y1$sortedVector
            x2 <- y2$sortedVector
            i1 <- 1
            i2 <- 1
            while(i1+i2 <= n1+n2+1){
                if(i2 > n2 || i1 <= n1 && x1[i1] <= x2[i2]){
                    x[i1+i2-1] <- x1[i1]
                    i1 <- i1 + 1
                } else {
                    inv <- inv + n1 + 1 - i1
                    x[i1+i2-1] <- x2[i2]
                    i2 <- i2 + 1
                }
            }
        }
        return (list(inversions=inv,sortedVector=x))
    }
    r <- mergeSort(x)
    return (r$inversions)
}
0 голосов
/ 09 августа 2016

другое решение Python

def inv_cnt(a):
n = len(a)
if n==1:
    return a,0
left = a[0:n//2] # should be smaller
left,cnt1 = inv_cnt(left)    
right = a[n//2:] # should be larger
right, cnt2 = inv_cnt(right)

cnt = 0    
i_left = i_right = i_a = 0
while i_a < n:
    if (i_right>=len(right)) or (i_left < len(left) and left[i_left] <= right[i_right]):
        a[i_a] = left[i_left]
        i_left += 1
    else:
        a[i_a] = right[i_right]
        i_right += 1              
        if i_left < len(left):
            cnt += len(left) - i_left        
    i_a += 1    

return (a, (cnt1 + cnt2 + cnt))
0 голосов
/ 12 января 2014

Java-реализация:

import java.lang.reflect.Array;
import java.util.Arrays;


public class main {

public static void main(String[] args) {
    int[] arr = {6, 9, 1, 14, 8, 12, 3, 2};
    System.out.println(findinversion(arr,0,arr.length-1));
}

public static int findinversion(int[] arr,int beg,int end) {
    if(beg >= end)
        return 0;

    int[] result = new int[end-beg+1];
    int index = 0;
    int mid = (beg+end)/2;
    int count = 0, leftinv,rightinv;
    //System.out.println("...."+beg+"   "+end+"  "+mid);
    leftinv = findinversion(arr, beg, mid);
    rightinv = findinversion(arr, mid+1, end);
    l1:
    for(int i = beg, j = mid+1; i<=mid || j<=end;/*index < result.length;*/ ) {
        if(i>mid) {
            for(;j<=end;j++)
                result[index++]=arr[j];
            break l1;
        }
        if(j>end) {
            for(;i<=mid;i++)
                result[index++]=arr[i];
            break l1;
        }
        if(arr[i] <= arr[j]) {
            result[index++]=arr[i];
            i++;    
        } else {
            System.out.println(arr[i]+"  "+arr[j]);
            count = count+ mid-i+1;
            result[index++]=arr[j];
            j++;    
        }
    }

    for(int i = 0, j=beg; i< end-beg+1; i++,j++)
        arr[j]= result[i];
    return (count+leftinv+rightinv);
    //System.out.println(Arrays.toString(arr));
}

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...