Минимальное количество перестановок, необходимое для изменения массива 1 на массив 2? - PullRequest
34 голосов
/ 07 июня 2010

Например, ввод:

Array 1 = [2, 3, 4, 5]
Array 2 = [3, 2, 5, 4]

Минимальное количество необходимых подстановок: 2.

Перестановки не обязательно должны выполняться со смежными ячейками, любые два элемента могут быть заменены.1007 *

Ответы [ 8 ]

27 голосов
/ 07 июня 2010

Как отметил @IVlad в комментарии к вашему вопросу Проблема Йоданесса просит подсчитать количество инверсий , а не минимальное количество свопов.

Например:

L1 = [2,3,4,5]
L2 = [2,5,4,3]

Минимальное количество свопов составляет один (поменяйте местами 5 и 3 в L2, чтобы получить L1), но число инверсий равно three : (5 4) (5 3) и (4 3) пары расположены в неправильном порядке.

Самый простой способ подсчета числа инверсий следует из определения :

Пара элементов (p i , p j ) называется инверсией в перестановке p, если i i > p J .

В Python:

def count_inversions_brute_force(permutation):
    """Count number of inversions in the permutation in O(N**2)."""
    return sum(pi > permutation[j]
               for i, pi in enumerate(permutation)
               for j in xrange(i+1, len(permutation)))

Вы можете считать инверсию в O(N*log(N)), используя стратегию «разделяй и властвуй» (аналогично алгоритму сортировки слиянием ). Вот псевдокод из Подсчет инверсий , переведенный в код Python:

def merge_and_count(a, b):
    assert a == sorted(a) and b == sorted(b)
    c = []
    count = 0
    i, j = 0, 0
    while i < len(a) and j < len(b):
        c.append(min(b[j], a[i]))
        if b[j] < a[i]:
            count += len(a) - i # number of elements remaining in `a`
            j+=1
        else:
            i+=1
    # now we reached the end of one the lists
    c += a[i:] + b[j:] # append the remainder of the list to C
    return count, c

def sort_and_count(L):
    if len(L) == 1: return 0, L
    n = len(L) // 2 
    a, b = L[:n], L[n:]
    ra, a = sort_and_count(a)
    rb, b = sort_and_count(b)
    r, L = merge_and_count(a, b)
    return ra+rb+r, L
* * Пример тысячу сорок четыре: * * 1045
>>> sort_and_count([5, 4, 2, 3])
(5, [2, 3, 4, 5])

Вот решение на Python для примера из проблема :

yoda_words   = "in the force strong you are".split()
normal_words = "you are strong in the force".split()
perm = get_permutation(normal_words, yoda_words)
print "number of inversions:", sort_and_count(perm)[0]
print "number of swaps:", number_of_swaps(perm)

Выход:

number of inversions: 11
number of swaps: 5

Определения get_permutation() и number_of_swaps():

def get_permutation(L1, L2):
    """Find permutation that converts L1 into L2.

    See http://en.wikipedia.org/wiki/Cycle_representation#Notation
    """
    if sorted(L1) != sorted(L2):
        raise ValueError("L2 must be permutation of L1 (%s, %s)" % (L1,L2))

    permutation = map(dict((v, i) for i, v in enumerate(L1)).get, L2)
    assert [L1[p] for p in permutation] == L2
    return permutation

def number_of_swaps(permutation):
    """Find number of swaps required to convert the permutation into
    identity one.

    """
    # decompose the permutation into disjoint cycles
    nswaps = 0
    seen = set()
    for i in xrange(len(permutation)):
        if i not in seen:           
           j = i # begin new cycle that starts with `i`
           while permutation[j] != i: # (i σ(i) σ(σ(i)) ...)
               j = permutation[j]
               seen.add(j)
               nswaps += 1

    return nswaps
12 голосов
/ 07 июня 2010

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

Мы должны рассматривать массив № 2 как преобразование перестановки в массиве № 1. В вашем примере перестановка может быть представлена ​​как P = [2,1,4,3].

Каждая перестановка может быть выражена как набор непересекающихся циклов, представляющих циклические изменения положения элементов. Перестановка P, например, имеет 2 цикла: (2,1) и (4,3). Поэтому двух свопов достаточно. В общем случае вы должны просто вычесть количество циклов из длины перестановки, и вы получите минимальное количество необходимых перестановок. Это следует из наблюдения, что для «исправления» цикла из N элементов достаточно N-1 перестановок.

3 голосов
/ 09 июня 2010

У этой проблемы есть чистое, жадное, тривиальное решение:

  1. Найдите любую операцию подкачки, которая обоих поменяет местами элементы в массиве Array1 ближе к месту назначения в массиве Array2. Выполните операцию подкачки для Array1, если она существует.
  2. Повторяйте шаг 1 до тех пор, пока больше не будет таких операций подкачки.
  3. Найти любую операцию подкачки, которая один поменял элемент в Array1 ближе к его месту назначения в Array2. Если такая операция существует, выполните ее для Array1.
  4. Вернитесь к шагу 1, пока Array1 == Array2.

Правильность алгоритма можно доказать, определив потенциал проблемы как сумму расстояний всех элементов в массиве 1 от их назначения в массиве 2.

0 голосов
/ 22 мая 2019

Поскольку мы уже знаем, что arr2 имеет правильные индексы каждого элемента, присутствующего в arr1. Поэтому мы можем просто сравнить элементы arr1 с arr2 и поменять их местами с правильными индексами, если они имеют неправильный индекс.

def minimum_swaps(arr1, arr2):
    swaps = 0
    for i in range(len(arr1)):
        if arr1[i] != arr2[i]: 
          swaps += 1
          element = arr1[i]
          index = arr1.index(arr2[i]) # find index of correct element
          arr1[index] = element # swap
          arr1[i] = arr2[i]    
    return swaps
0 голосов
/ 08 ноября 2017

Алгоритм:

  1. Проверьте, равны ли элементы списка в одной и той же позиции.Если да, обмен не требуется.Если нет, поменяйте местами элемент списка, где совпадает элемент
  2. Итерация процесса для всех элементов списка.

Код:

def nswaps(l1, l2):
    cnt = 0
    for i in range(len(l1)):
        if l1[i] != l2[i]:
            ind = l2.index(l1[i])
            l2[i], l2[ind] = l2[ind], l2[i]
            cnt += 1
        pass
    return cnt
0 голосов
/ 21 июля 2016

Это может быть легко преобразовано в проблему другого типа, которая может быть решена более эффективно. Все, что нужно, - это преобразовать массивы в перестановки, то есть изменить значения на их идентификаторы. Итак, ваши массивы:

L1 = [2,3,4,5]
L2 = [2,5,4,3]

станет

P1 = [0,1,2,3]
P2 = [0,3,2,1]

с присвоением 2->0, 3->1, 4->2, 5->3. Это может быть сделано только если нет повторяющихся предметов, хотя. Если есть, то это становится труднее решить.

Преобразование перестановки из одной в другую может быть преобразовано в аналогичную проблему ( Число перестановок в перестановке ) путем инвертирования целевой перестановки в O (n), составления перестановок в O (n) и затем найти число перестановок оттуда к тождественной перестановке в O (m). Дано:

int P1[] = {0, 1, 2, 3}; // 2345
int P2[] = {0, 3, 2, 1}; // 2543

// we can follow a simple algebraic modification
// (see http://en.wikipedia.org/wiki/Permutation#Product_and_inverse):
// P1 * P = P2                   | premultiply P1^-1 *
// P1^-1 * P1 * P = P1^-1 * P2
// I * P = P1^-1 * P2
// P = P1^-1 * P2
// where P is a permutation that makes P1 into P2.
// also, the number of steps from P to identity equals
// the number of steps from P1 to P2.

int P1_inv[4];
for(int i = 0; i < 4; ++ i)
    P1_inv[P1[i]] = i;
// invert the first permutation in O(n)

int P[4];
for(int i = 0; i < 4; ++ i)
    P[i] = P2[P1_inv[i]];
// chain the permutations in O(n)

int num_steps = NumSteps(P, 4); // will return 2
// now we just need to count the steps in O(num_steps)

Для подсчета шагов может быть разработан простой алгоритм, такой как:

int NumSteps(int *P, int n)
{
    int count = 0;
    for(int i = 0; i < n; ++ i) {
        for(; P[i] != i; ++ count) // could be permuted multiple times
            swap(P[P[i]], P[i]); // look where the number at hand should be
    }
    // count number of permutations

    return count;
}

Это всегда меняет предмет на место, где он должен находиться в перестановке идентификаторов, поэтому на каждом шаге он отменяет и учитывает один обмен. Теперь, при условии, что количество возвращаемых свопов действительно минимально, время выполнения алгоритма ограничено им и гарантированно завершится (вместо того, чтобы застрять в бесконечном цикле). Он будет выполняться в O(m) swaps или O(m + n) loop циклах, где m - это количество свопов (возвращается count), а n - количество элементов в последовательности (4). Обратите внимание, что m < n всегда верно. Следовательно, это должно быть лучше, чем O(n log n) решения, так как верхняя граница здесь равна O(n - 1) свопов или O(n + n - 1) итераций цикла, что практически равно O(n) (постоянный коэффициент 2 в последнем случае опущен).

Алгоритм будет работать только для допустимых перестановок, он будет бесконечно циклически повторяться для последовательностей с дублирующимися значениями и будет осуществлять доступ к массиву вне пределов (и сбой) для последовательностей со значениями, отличными от [0, n). Полный тестовый пример можно найти здесь (сборка с Visual Studio 2008, сам алгоритм должен быть достаточно переносимым). Он генерирует все возможные перестановки длины от 1 до 32 и проверяет решения, сгенерированные с помощью поиска в ширину (BFS), кажется, работает для всех перестановок длины от 1 до 12, затем он становится довольно медленным, но я предполагаю, что он просто продолжит работать .

0 голосов
/ 04 июня 2015

@J.F. Ответ Себастьяна и @Eyal Schneider довольно крутой. Меня вдохновило решение аналогичной проблемы: Рассчитайте минимальные свопы, необходимые для сортировки массива , например: для сортировки {2,1,3,0}, вам нужно минимум 2 свопа.

Вот код Java:

// 0 1 2 3
// 3 2 1 0  (0,3) (1,2)
public static int sortWithSwap(int [] a) {
    Integer[] A = new Integer[a.length];
    for(int i=0; i<a.length; i++)   A[i] = a[i];
    Integer[] B = Arrays.copyOf(mapping(A), A.length, Integer[].class);

    int cycles = 0;
    HashSet<Integer> set = new HashSet<>();
    boolean newCycle = true;
    for(int i=0; i<B.length; ) {
        if(!set.contains(B[i])) {
            if(newCycle) {
                newCycle = false;
                cycles++;
            }
            set.add(B[i]);
            i = B[i];
        }
        else if(set.contains(B[i])) {   // duplicate in existing cycles
            newCycle = true;
            i++;
        }
    }

    // suppose sequence has n cycles, each cycle needs swap len(cycle)-1 times
    // and sum of length of all cycles is length of sequence, so
    // swap = sequence length - cycles
    return a.length - cycles;
}

// a b b c
// c a b b
// 3 0 1 1
private static Object[] mapping(Object[] A) {
    Object[] B = new Object[A.length];
    Object[] ret = new Object[A.length];
    System.arraycopy(A, 0, B, 0, A.length);
    Arrays.sort(A);
    HashMap<Object, Integer> map = new HashMap<>();
    for(int i=0; i<A.length; i++) {
        map.put(A[i], i);
    }

    for(int i=0; i<B.length; i++) {
        ret[i] = map.get(B[i]);
    }
    return ret;
}
0 голосов
/ 07 июня 2010

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

Проверьте Расстояние Дамерау – Левенштейна псевдокод. Я полагаю, что вы можете настроить его так, чтобы он учитывал только транспозиции.

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