我们给出了两个小写拉丁字母字母序列。 它们的长度相同,并且具有相同数量的给定类型 字母(第一个具有相同数量的t作为第二个等等 上)。我们需要找到最小交换次数( by“swap”我们的意思是改变 要求的两个相邻 字母的顺序 将第一个序列转换为第二个序列。我们 可以安全地假设每两个序列都可以转换 相互进入。我们可以用蛮力做到这一点,但序列太长了。
输入:
序列的长度(至少2,最多999999)和 那么两个序列。输出:
一个整数,表示所需的交换次数 序列变得相同。示例:
{5,aaaaa,aaaaa}应输出{0},
{4,abcd,acdb}应输出{2}。
我想到的第一件事是bubblesort。我们可以简单地对每个交换的序列进行计数。问题是:a)它是O(n ^ 2)最坏情况b)我不相信它会给我每个案例的最小数字......即使是优化的bubblesort似乎也没有做到这一点。我们可以实施鸡尾酒种类来解决海龟的问题 - 但它会给我最好的表现吗?或者也许有更简单/更快的东西?
这个问题也可以表述为:当允许的唯一操作是换位时,我们如何确定两个字符串之间的编辑距离?
答案 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
P
有2
次反转(4 2
和4 3
),所以这就是答案。
此解决方案为O(n log n)
,因为可以在P
中构建Q
和O(n)
,并且合并排序可以计算O(n log n)
中的反转。
答案 2 :(得分:7)
您正在寻找的内容可能与“ Kendall tau距离”相同,后者是一致的负不一致对的(标准化)差异。请参阅Wikipedia,声称它等同于冒泡排序距离。
在R中,函数不仅可用于计算tau,例如
cor( X, method="kendall", use="pairwise" ) ,
但也用于测试差异的重要性,例如
cor.test( x1, x2, method="kendall" ) ,
他们甚至能够正确地考虑到关系。
答案 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)
答案 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。