将阵列1更改为阵列2所需的最小交换次数?

时间:2010-06-07 07:00:16

标签: algorithm

例如,输入是

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

所需的最小交换次数为2

交换不需要与相邻的单元格相交,任何两个元素都可以交换。

9 个答案:

答案 0 :(得分:27)

正如@IVlad在对您的问题Yodaness problem的评论中指出的那样,要求您计算number of inversions而不是最小数量的互换。

例如:

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

交换的最小数量是一个(在L2中交换5和3以获得L1),但转换次数 3 :(5 4),(5 3)和(4 3)对的顺序错误。

计算倒数数量的最简单方法是the definition

  

如果i <1,则一对元素(p i ,p j )在置换p中被称为反转。 j和p i &gt; P <子>Ĵ

在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)))

你可以使用divide&amp;&amp;征服战略(类似于a merge sort algorithm如何运作)。这里的Counting Inversions伪代码转换为Python代码:

O(N*log(N))

示例:

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

这是来自the problem的示例的Python解决方案:

>>> sort_and_count([5, 4, 2, 3])
(5, [2, 3, 4, 5])

输出:

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()

答案 1 :(得分:11)

正如Sebastian的解决方案所暗示的那样,您正在寻找的算法可以基于检查permutation's cycles

我们应该将数组#2视为数组#1上的置换变换。在您的示例中,置换可以表示为P = [2,1,4,3]。

每个排列可以表示为一组不相交的循环,表示项目的循环位置变化。排列P例如具有2个循环:(2,1)和(4,3)。因此,两次交换就足够了。在一般情况下,您应该简单地从排列长度中减去周期数,并获得所需交换的最小数量。这是因为观察到为了“修复”N个元素的循环,N-1交换就足够了。

答案 2 :(得分:3)

这个问题有一个干净,贪婪,简单的解决方案:

  1. 查找任何交换操作,使Array1中的两个交换元素更接近Array2中的目标。对Array1执行交换操作(如果存在)。
  2. 重复步骤1,直到不再存在此类交换操作。
  3. 查找任何交换操作,使Array1中的一个交换元素更接近Array2中的目标。如果存在此类操作,请在Array1上执行。
  4. 返回步骤1,直到Array1 == Array2。
  5. 通过将问题的可能性定义为array1中所有元素与array2中目标的距离之和,可以证明算法的正确性。

答案 3 :(得分:1)

可能有一些智能动态编程解决方案,但我现在无法弄清楚。您可以使用以下内容进行天真的BFS遍历:

  • 断言可能(例如通过排序和比较)
  • 队列q = [(0,L1)]
  • 虽然q非空
    • 提取一对(i,L)
    • 如果L == L2,则返回i
    • 对于每个0 <= i,j <= L1.length
      • 将(i + 1,L.swap(i,j))添加到q

UPDATE :在Python中实现(缓慢的O((N 3 n ))

def bfs(L1, L2):
    assert sorted(L1) == sorted(L2)
    q = deque([ (0,L1) ])
    while q:
        n, L = q.popleft()
        if L == L2: return n
        for i, j in combinations(range(len(L1)), 2): # all N*(N-1)/2 pairs
            q.append((n+1, swap(L, i, j)))

from collections import deque
from itertools   import combinations    

def swap(L, i, j):
    a = list(L) # make copy
    a[i], a[j] = a[j], a[i] # swap
    return a

答案 4 :(得分:1)

这可以很容易地转换为另一种类型的问题,可以更有效地解决。所需要的只是将数组转换为排列,即将值更改为其id。所以你的阵列:

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)中的置换,然后从中找到交换的数量,可以将置换从一个转换为另一个转换为类似的问题(Number of swaps in a permutation)在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)交换或O(m + n)循环迭代中运行,其中m是交换次数(返回count),n是序列中的项目数(4)。请注意m < n始终为真。因此,这应该优于O(n log n)解,因为这里的上限是交换的O(n - 1)或者循环迭代的O(n + n - 1),这实际上是O(n)(常数因子)在后一种情况下省略了2)。

该算法仅适用于有效排列,对于具有重复值的序列,它将无限循环,并且对于值不是[0, n)的序列,将对范围内的数组进行访问(和崩溃)。可以找到一个完整的测试用例here(使用Visual Studio 2008构建,算法本身应该相当便携)。它生成长度为1到32的所有可能的排列,并且检查通过广度优先搜索(BFS)生成的解决方案似乎适用于长度为1到12的所有排列,然后它变得相当慢但我认为它将继续工作

答案 5 :(得分:0)

这似乎是一个edit distance问题,除了只允许换位。

查看Damerau–Levenshtein distance伪代码。我相信你可以调整它只计算换位。

答案 6 :(得分:0)

<强>算法:

  1. 检查相同位置的列表元素是否相等。如果是,则不需要交换。如果不是,则在元素匹配的任何地方交换list-element的位置
  2. 迭代整个列表元素的过程。
  3. <强>代码:

    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
    

答案 7 :(得分:0)

由于我们已经知道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

答案 8 :(得分:-1)

@ J.F。 Sebastian和@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;
}