8

我有一些元素数量可变的列表。每个列表都已排序,但排序算法未知。我想将列表合并成一个大列表,其中包含所有列表的顺序相同,没有重复。

示例输入:

  1. XS,M,L,XL
  2. S,M,XXL
  3. XXS,XS,S,L

预期结果:

  • XXS,XS,S,M,L,XL,XXL

预期的结果是通过匹配输入序列来获得一个合并的结果,该结果包含每个输入序列的元素以正确的顺序排列,如下所示:

    XS   M L XL
       S M      XXL
XXS XS S   L
-------------------
XXS XS S M L XL XXL

如果存在位置不明确的元素,该函数应通知。在这里,它将是 XXL(它可以留在 M、L 或 XL 之后),我需要在 XL 之后手动指定它的位置(因为在这里我知道排序算法并且可以提供帮助)。我考虑过定义每两个元素的对,每对按原始列表中的顺序排列。从这个可以建立完整的列表。

4

4 回答 4

14

这可以通过拓扑排序算法来解决。

如果您将每个输入序列视为通过有向图的路径,则拓扑排序将按照每个有向边指向右侧的方式从左到右对您的节点集进行排序。 拓扑排序后的有向图示意图

拓扑排序的维基百科页面包括这个算法,由 Arthur Kahn 在 1962 年首次描述:

L ← Empty list that will contain the sorted elements
S ← Set of all nodes with no incoming edges
while S is non-empty do
    remove a node n from S
    insert n into L
    for each node m with an edge e from n to m do
        remove edge e from the graph
        if m has no other incoming edges then
            insert m into S
if graph has edges then
    return error (graph has at least one cycle)
else 
    return L (a topologically sorted order)

这个算法,如所写,如果它找到不明确的序列,它实际上并不会失败,但是通过在循环的开头插入一个检查很容易添加,如下所示:

...
while S is non-empty do
    if S contains more than 1 item
        return error (inputs are ambiguous)
    remove a node n from S
    ...

我不知道您使用的是哪种语言,但我将这个 PHP 实现放在一起作为概念证明:

function mergeSequences($sequences, $detectAmbiguity = false) {

    // build a list of nodes, with each node recording a list of all incoming edges
    $nodes = array();
    foreach ($sequences as $seq) {
        foreach ($seq as $i => $item) {
            if (!isset($nodes[$item])) $nodes[$item] = array();
            if ($i !== 0) {
                $nodes[$item][] = $seq[$i-1];
            }
        }
    }

    // build a list of all nodes with no incoming edges
    $avail = array();
    foreach ($nodes as $item => $edges) {
        if (count($edges) == 0) {
            $avail[] = $item;
            unset($nodes[$item]);
        }
    }

    $sorted = array();
    $curr = '(start)';
    while (count($avail) > 0) {

        // optional: check for ambiguous sequence
        if ($detectAmbiguity && count($avail) > 1) {
           throw new Exception("Ambiguous sequence: {$curr} can be followed by " . join(' or ', $avail));
        }

        // get the next item and add it to the sorted list
        $curr = array_pop($avail);
        $sorted[] = $curr;

        // remove all edges from the currently selected items to all others
        foreach ($nodes as $item => $edges) {
            $nodes[$item] = array_diff($edges, array($curr));                
            if (count($nodes[$item]) == 0) {
                $avail[] = $item;
                unset($nodes[$item]);
            }
        }

    }

    if (count($nodes) > 0) {
        throw new Exception('Sequences contain conflicting information. Cannot continue after: ' . join(', ', $sorted));
    }

    return $sorted;
}

您可以像这样调用该函数:

$input = array(
    array('XS', 'M', 'L', 'XL'),
    array('S', 'M', 'XXL'),
    array('XXS', 'XS', 'S', 'L'),
);
echo(join(', ', mergeSequences($input)));
echo(join(', ', mergeSequences($input, true)));

要获得以下输出:

XXS, XS, S, M, XXL, L, XL
Uncaught exception 'Exception' with message 'Ambiguous sequence: M can be followed by L or XXL'
于 2013-06-19T23:23:48.520 回答
6

您正在尝试合并部分有序的集合或 poset。合并的模棱两可的部分称为反链。因此,您需要一种算法来合并 poset 并告诉您反链是什么。

这是一篇论文,描述了一种用于合并 poset 和检测反链的算法,以及到第一作者主页的链接,以防您想联系他以查看是否有任何可用的源代码。

于 2013-06-20T16:26:32.020 回答
3

这是我要做的:

  1. 预处理列表:找出 ​​XXS 小于 XS 小于 S 小于 ... XXL 是一个[约束满足问题](http://en.wikipedia.org/wiki/Constraint_satisfaction_problem)。这种类型的问题涉及在给定原始列表中定义的约束的情况下在所有元素中搜索正确的排序。
  2. 完成步骤 1 后,创建从集合 {XXS, ..., XXL} 到集合 {1, ..., 6} 的双向映射。
  3. 对于每个列表,使用 2 中定义的映射创建另一个列表。
  4. 使用修改后的 [合并排序](http://en.wikipedia.org/wiki/Merge_sort) 来合并两个列表。修改合并算法,以便它报告正在比较的两个项目是否相同(并忽略正在合并的项目之一)。
  5. 对每对列表执行第 4 步,直到有一个列表。
  6. 使用 2 中定义的映射,创建列表的文本版本。
于 2011-01-10T21:03:36.643 回答
-1

对于排序部分,根据您的描述,我认为合并排序就足够了。需要修改的一点是在合并期间,如果输入数组的第一个元素与结果数组相同,我们应该跳过输入数组中的元素。

如果我理解正确,您想要构建所有可能输入元素的总顺序。输入数组中已经定义了一些部分顺序(因为它们已经排序),而其他部分需要用户指定。例如在问题中,订购

'S'<'M'<'XXL'

'XS'<'M'<'L'<'XL'

'XXS'<'XS'<'S'<'L'

定义明确。但是算法仍然不知道'XXL'是大于还是小于'XL','L'。
好吧,由于三个输入数组已排序,因此输入元素必须存在总顺序。所以我的建议是向您的数据提供者询问所有可能的数据元素的有序列表。听起来很愚蠢,但这是一个简单的方法。

如果这个列表不可用,一个简单的处理方法是提示用户进行配对排序,然后在算法遇到歧义配对时检查这是否与现有输入序列冲突并记住它。我认为拓扑排序比这个应用程序更强大。由于我们处理单个数据元素,因此必须退出总订单。而拓扑排序是为了处理偏序。

于 2013-06-26T21:39:05.940 回答