计算将一个排列转换为另一个排列所需的相邻交换

时间:2011-10-17 17:49:40

标签: algorithm

  

我们给出了两个小写拉丁字母字母序列。   它们的长度相同,并且具有相同数量的给定类型   字母(第一个具有相同数量的t作为第二个等等   上)。我们需要找到最小交换次数( by“swap”我们的意思是改变   要求的两个相邻 字母的顺序   将第一个序列转换为第二个序列。我们   可以安全地假设每两个序列都可以转换   相互进入。我们可以用蛮力做到这一点,但序列太长了。

     

输入:
  序列的长度(至少2,最多999999)和   那么两个序列。

     

输出:
  一个整数,表示所需的交换次数   序列变得相同。

     

示例:
  {5,aaaaa,aaaaa}应输出{0},
  {4,abcd,acdb}应输出{2}。

我想到的第一件事是bubblesort。我们可以简单地对每个交换的序列进行计数。问题是:a)它是O(n ^ 2)最坏情况b)我不相信它会给我每个案例的最小数字......即使是优化的bubblesort似乎也没有做到这一点。我们可以实施鸡尾酒种类来解决海龟的问题 - 但它会给我最好的表现吗?或者也许有更简单/更快的东西?

这个问题也可以表述为:当允许的唯一操作是换位时,我们如何确定两个字符串之间的编辑距离?

6 个答案:

答案 0 :(得分:12)

关于将置换转换为另一个所需的最小(不一定是相邻)交换次数,您应该使用的度量是Cayley距离,它基本上是置换的大小 - 周期数。

计算排列中的循环次数是一个非常微不足道的问题。一个简单的例子。假设排列521634。

如果你检查第一个位置,你有5个,在第5个你有3个,在第3个你有1个,关闭第一个周期。 2处于第2位置,因此它自己进行循环,4和6进行最后一个循环(4位于第6位,6位位于第4位)。如果要在身份排列中转换此排列(使用最小交换次数),则需要单独重新排序每个循环。交换总数是排列长度(6)减去周期数(3)。

给定任意两个排列,它们之间的距离等于第一个的composition与第二个的倒数之间的距离和身份(如上所述计算)。因此,您唯一需要做的就是组合第一个排列和第二个排列的倒数,并计算结果中的循环数。所有这些操作都是O(n),因此您可以在线性时间内获得最小数量的交换。

答案 1 :(得分:10)

这是一个简单而有效的解决方案:

Q[ s2[i] ] = the positions character s2[i] is on in s2。让P[i] = on what position is the character corresponding to s1[i] in the second string

构建Q和P:

for ( int i = 0; i < s1.size(); ++i )
    Q[ s2[i] ].push_back(i); // basically, Q is a vector [0 .. 25] of lists

temp[0 .. 25] = {0}
for ( int i = 0; i < s1.size(); ++i )
    P[i + 1] = 1 + Q[ s1[i] ][ temp[ s1[i] ]++ ];

示例:

    1234
s1: abcd
s2: acdb
Q: Q[a = 0] = {0}, Q[b = 1] = {3}, Q[c = 2] = {1}, Q[d = 3] = {2}
P: P[1] = 1, P[2] = 4 (because the b in s1 is on position 4 in s2), P[3] = 2
   P[4] = 3

P2次反转(4 24 3),所以这就是答案。

此解决方案为O(n log n),因为可以在P中构建QO(n),并且合并排序可以计算O(n log n)中的反转。

答案 2 :(得分:7)

您正在寻找的内容可能与“ Kendall tau距离”相同,后者是一致的负不一致对的(标准化)差异。请参阅Wikipedia,声称它等同于冒泡排序距离。

在R中,函数不仅可用于计算tau,例如

cor( X, method="kendall", use="pairwise" ) ,

但也用于测试差异的重要性,例如

cor.test( x1, x2, method="kendall" ) ,

他们甚至能够正确地考虑到关系。

See here for more.

答案 3 :(得分:2)

我编写了一个类Permutation,除了其他功能之外,它还可以返回将给定的排列转换为标识所需的一些转置。这是通过创建轨道(循环)和计算它们的长度来完成的。术语取自 Kostrikin A.,I。,&#34; Linear Algebra I&#34; 简介。

包括:

#include <iostream>
#include <vector>
#include <set>
#include <algorithm>
#include <iterator>

class Permutation:

class Permutation {
public:
    struct ei_element {    /* element of the orbit*/
        int e; /* identity index */
        int i; /* actual permutation index */
    };
    typedef std::vector<ei_element> Orbit; /* a cycle */

    Permutation( std::vector<int> const& i_vector);
    /* permute i element, vector is 0 indexed */
    int pi( int i) const { return iv[ i - 1]; }
    int i( int k) const { return pi( k); } /* i_k = pi(k) */
    int q() const { /* TODO: return rank = q such that pi^q = e */ return 0; }
    int n() const { return n_; }
    /* return the sequence 1, 2, ..., n */
    std::vector<int> const& Omega() const { return ev; }
    /* return vector of cycles */
    std::vector<Orbit> const& orbits() const { return orbits_; }
    int l( int k) const { return orbits_[ k].size(); } /* length of k-th cycle */
    int transpositionsCount() const;  /* return sum of all transpositions */
    void make_orbits();

    private:
    struct Increment {
        int current;
        Increment(int start) : current(start) {}
        int operator() () {
            return current++;
        }
    };
    int n_;
    std::vector<int> iv; /* actual permutation */
    std::vector<int> ev; /* identity permutation */
    std::vector<Orbit> orbits_;
};

说明:

Permutation::Permutation( std::vector<int> const& i_vector) : 
                                                      n_( i_vector.size()), 
                                                      iv( i_vector), ev( n_) {
        if ( n_) { /* fill identity vector 1, 2, ..., n */
            Increment g ( 1);
            std::generate( ev.begin(), ev.end(), g);
        }
}

/* create orbits (cycles) */
void Permutation::make_orbits() {
    std::set<int> to_visit( ev.begin(), ev.end()); // identity elements to visit
    while ( !to_visit.empty()) {
        /* new cycle */
        Orbit orbit;
        int first_to_visit_e = *to_visit.begin();
        to_visit.erase( first_to_visit_e);
        int k = first_to_visit_e; // element in identity vector
        /* first orbit element */
        ei_element element;
        element.e = first_to_visit_e;
        element.i = i( first_to_visit_e);
        orbit.push_back( element);
        /* traverse permutation until cycle is closed */
        while ( pi( k) != first_to_visit_e && !to_visit.empty()) {
            k = pi( k);
            ei_element element;
            element.e = k;
            element.i = pi( k);
            orbit.push_back( element);
            to_visit.erase( k);
        }
        orbits_.push_back( orbit);
    }
}

/* return sum of all transpositions */
int Permutation::transpositionsCount() const {
    int count = 0;
    int k = 0;
    while ( k < orbits_.size()) {
        count += l( k++) - 1; /* sum += l_k - 1 */ 
    }
    return count;
}

用法:

/*
 * 
 */
int main(int argc, char** argv) {
                       //1, 2, 3, 4, 5, 6, 7, 8       identity (e)
    int permutation[] = {2, 3, 4, 5, 1, 7, 6, 8}; //  actual (i)
    std::vector<int> vp( permutation, permutation + 8);

    Permutation p( vp);
    p.make_orbits();
    int k = p.orbits().size();
    std::cout << "Number of cycles:" << k << std::endl;

    for ( int i = 0; i < k; ++i) {
        std::vector<Permutation::ei_element> v = p.orbits()[ i];
        for ( int j = 0; j < v.size(); ++j) {
            std::cout << v[ j].e << "," << v[ j].i << " | ";
        }
        std::cout << std::endl;
    }

    std::cout << "Steps needed to create identity permutation: " 
                                                << p.transpositionsCount();
    return 0;
}

输出:

周期数:3

1,2 | 2,3 | 3,4 | 4,5 | 5,1 |

6,7 | 7,6 |

8,8 |

创建身份置换所需的步骤:5

RUN SUCCESSFUL(总时间:82ms)


coliru

答案 4 :(得分:2)

通过反转O(n)中的目标置换,组成O(n)中的置换,然后从中找到交换的数量,可以将置换从一个转换为另一个转换为类似的问题(Number of swaps in a permutation)那里有一个身份排列。 鉴于:

int P1[] = {0, 1, 2, 3}; // abcd
int P2[] = {0, 2, 3, 1}; // acdb

// 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 O(n)

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

int num_steps = NumSteps(P, 4); // will return 2
// now we just need to count the 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 :(得分:2)

Kendall tau距离”算法是这种情况下的精确解决方案,其中必须找到相邻元素的交换次数

实施例

  

eyssaasse(基本字符串
  seasysaes

基本字符串为每个元素提供索引: e = 0, y = 1, s = 2 , s = 3, a = 4, a = 5, s = 6, s < / strong> = 7, e = 8;

有些元素是重复的,所以:
1)创建字典,其中元素是键,值是索引列表:

  

idx = {' e '=&gt; [0,8],' y '=&gt; [1],' s '=&gt; [2,3,6,7],' a '=&gt; [4,5]}

2)使用 idx 字典中的元素索引创建第二个字符串的索引映射:

  

seasysaes - &gt; 204316587 (循环'seasysaes'并从 idx 中的每个键的列表中弹出下一个索引)

3)创建此地图的所有配对组合的列表,204316587:20 24 23 21 26 25 28 27 04 03 01 06 ... 65 68 67 58 57 87;
循环通过这些对计数第一个数字大于第二个数字的那些 此计数是字符串之间相邻互换所需的数量

Python脚本:

from itertools import combinations, cycle

word = 'eyssaasse' # base string
cmpr = 'seasysaes' # a string to find number of swaps from the base string
swaps = 0

# 1)
chars = {c: [] for c in word}
[chars[c].append(i) for i, c in enumerate(word)]
for k in chars.keys():
    chars[k] = cycle(chars[k])

# 2)
idxs = [next(chars[c]) for c in cmpr]

# 3)
for cmb in combinations(idxs, 2):
    if cmb[0] > cmb[1]:
        swaps += 1

print(swaps)

'eyssaasse'和'seasysaes'之间的互换次数为7 对于'reviver'和'vrerevi',它是8。