如何比较两个列表在同一顺序中的程度?

时间:2008-12-18 15:36:29

标签: algorithm

我有两个包含相同元素的数组,但顺序不同,我想知道它们的订单差异程度。

我试过的方法不起作用。它如下:

对于每个列表,我构建了一个矩阵,该矩阵为每对元素记录它们在列表中是高于还是低于对方。然后我计算了这两个矩阵的皮尔森相关系数。这非常糟糕。这是一个简单的例子:

list 1:
1
2
3
4

list 2:
1
3
2
4

我上面描述的方法产生了这样的矩阵(其中1表示行号高于列,0表示反之亦然):

list 1:
  1 2 3 4
1   1 1 1
2     1 1
3       1
4

list 2:
  1 2 3 4 
1   1 1 1
2     0 1 
3       1
4

由于唯一的区别是元素2和3的顺序,因此应视为非常相似。这两个矩阵的Pearson相关系数为0,表明它们根本没有相关性。我想问题是我正在寻找的不是真正的相关系数,而是一些其他类型的相似性度量。编辑距离,也许?

有人可以提出更好的建议吗?

8 个答案:

答案 0 :(得分:11)

每个元素的指数差异的均方。

List 1: A B C D E
List 2: A D C B E

列表2中列表1的每个元素的索引(从零开始)

A B C D E
0 3 2 1 4

列表1中列表1的每个元素的索引(从零开始)

A B C D E
0 1 2 3 4

差异:

A  B C D E
0 -2 0 2 0

差异之处:

A B C D E
  4   4

平均差异= 8/5。

答案 1 :(得分:2)

只是一个想法,但是在调整标准排序算法以计算将list1转换为list2所需的交换操作数量方面是否有任何影响?

我认为定义比较函数可能很困难(甚至可能与原始问题一样困难!),这可能效率低下。

编辑:考虑到这一点,比较函数基本上由目标列表本身定义。例如,如果列表2是:

1 4 6 5 3

...然后比较函数应该导致1< 4< 6< 5< 3(并返回条目相等的相等)。

然后只需要扩展交换功能来计算交换操作。

答案 2 :(得分:1)

您可能会考虑将一个字符串转换为另一个字符串所需的更改次数(我猜您在提到编辑距离时就是这样做了。)

请参阅:http://en.wikipedia.org/wiki/Levenshtein_distance

虽然我认为l-distance不考虑旋转。如果允许旋转作为操作,则:

1,2,3,4

2,3,4,1

非常相似。

答案 3 :(得分:1)

这里的派对有点晚了,但仅仅是为了记录,我认为Ben几乎拥有它...如果你进一步研究相关系数,我想你已经发现 Spearman的等级相关系数可能是最佳选择。

有趣的是,jamesh似乎已经得出了类似的衡量标准,但没有标准化。

请参阅此recent SO answer

答案 4 :(得分:0)

有一个分支定界算法可以适用于您喜欢的任何运算符集。它可能不是很快。伪代码就是这样的:

bool bounded_recursive_compare_routine(int* a, int* b, int level, int bound){
    if (level > bound) return false;
    // if at end of a and b, return true
    // apply rule 0, like no-change
    if (*a == *b){
        bounded_recursive_compare_routine(a+1, b+1, level+0, bound);
        // if it returns true, return true;
    }
    // if can apply rule 1, like rotation, to b, try that and recur
    bounded_recursive_compare_routine(a+1, b+1, level+cost_of_rotation, bound);
    // if it returns true, return true;
    ...
    return false;
}

int get_minimum_cost(int* a, int* b){
    int bound;
    for (bound=0; ; bound++){
        if (bounded_recursive_compare_routine(a, b, 0, bound)) break;
    }
    return bound;
}

它所花费的时间在答案中大致呈指数级,因为它由最后一个有效的边界支配。

补充:这可以扩展为查找存储在trie中的最近匹配字符串。我几年前用拼写校正算法做过。

答案 5 :(得分:0)

我不确定它在引擎盖下使用的确切公式,但difflib.SequenceMatcher.ratio()正是如此:

ratio(self) method of difflib.SequenceMatcher instance:
    Return a measure of the sequences' similarity (float in [0,1]).

代码示例:

from difflib import SequenceMatcher
sm = SequenceMatcher(None, '1234', '1324')
print sm.ratio()

>>> 0.75

答案 6 :(得分:0)

基于一点mathematics的另一种方法是计算反转次数以将其中一个数组转换为另一个数组。 反转是两个相邻数组元素的交换。在红宝石中它是这样做的:

# extend class array by new method
class Array
  def dist(other)
    raise 'can calculate distance only to array with same length' if length != other.length
    # initialize count of inversions to 0
    count = 0
    # loop over all pairs of indices i, j with i<j
    length.times do |i|
      (i+1).upto(length) do |j|
        # increase count if i-th and j-th element have different order
        count += 1 if (self[i] <=> self[j]) != (other[i] <=> other[j])
      end
    end
    return count
  end
end
l1 = [1, 2, 3, 4]
l2 = [1, 3, 2, 4]
# try an example (prints 1)
puts l1.dist(l2)

两个长度为n的数组之间的距离可以在0(它们是相同的)和n *(n + 1)/ 2之间(反转第一个数组得到第二个)。如果您希望距离始终在0和1之间,以便能够比较不同长度的数组对的距离,则除以n *(n + 1)/ 2.

该算法的缺点是运行时间为n ^ 2。它还假设数组没有双重条目,但可以进行调整。

关于代码行“count + = 1 if ......”的注释:只有当第一个列表的第i个元素小于小于第j个时,才会增加计数元素和第二个列表的第i个元素比其第j个元素更大,反之亦然(意味着第一个列表的第i个元素大于第j个元素,第二个列表的第i个元素小于第j个元素。简而言之:(l1 [i]&lt; l1 [j]和l2 [i]> l2 [j])或(l1 [i]> l1 [j]和l2 [i]&lt; l2 [j])

答案 7 :(得分:0)

如果有两个订单,则应查看两个重要的排名相关系数:

  1. 斯皮尔曼等级相关系数:https://en.wikipedia.org/wiki/Spearman%27s_rank_correlation_coefficient 这与Jamesh答案几乎相同,但在-1到1的范围内缩放。 它被定义为:
    1 - (6 * sum_of_squared_distances)/(n_samples *(n_samples ** 2 - 1)

  2. Kendalls tau:https://nl.wikipedia.org/wiki/Kendalls_tau

  3. 使用python时可以使用:

     from scipy import stats
    
     order1 = [ 1, 2, 3, 4]
     order2 = [ 1, 3, 2, 4]
     print stats.spearmanr(order1, order2)[0]
     >> 0.8000
     print stats.kendalltau(order1, order2)[0]
     >> 0.6667