从以下树状数据结构中获取所有可能的组合

时间:2017-04-20 15:47:39

标签: java algorithm recursion tree

在下面的内容中,您将看到一个数据结构,它非常类似于功能模型的简化版本(因为它们具有外观here)并且是树的某个版本。我已经用Java实现了数据结构,现在我试着去分析它。具体而言,我希望获得在给定特定特征模型时可能的所有元素组合。

假设我们有以下特征模型: Simplified feature model

元素是方框。未填充的圆圈表示可选元素(请记住o表示可选),填充的表示强制。必须包含根元素。由于这些规则,以下组合或元素列表是可能的:

  • [1,3,5,6]
  • [1,2,3,5,6]
  • [1,3,4,5,6,7]
  • [1,2,3,4,5,6,7]

现在,我想通过可视化搜索来获取它们,而不是通过Java程序。为此,我实施了几个课程:

  • Tree存储整个要素模型(图形)。
  • Element是其中一个矩形。
  • Edge描述了直线和圆圈。
  • Combination拥有一种可能的组合(组合列表)。

Element课程中,我实施了一项功能,以获取名为recursiveConfiguration的所有组合:

public List<Combination> recursiveCombination(List<Combination> combinations){

    for(Combination c: combinations){
        c.add(this);
    }

    // Iterate through all edges of the current element
    for(Edge e: getEdges()){

        int type = e.getType();         

        // In this case getChildren always returns only one feature.
        Element child = e.getChildren().get(0);             
        List<Combination> childCombinations = child.recursiveCombination(combinations);

        if(type == 1){
            // If type is mandatory
            combinations = childCombinations; // Line with compiler error
        }else{
            // If type is optional
            combinations.addAll(childCombinations);
        }    
    }   
    return combinations;
}

算法说明: 它使用一个元素的组合列表进行调用,这是一个空列表。在根元素1上调用该函数。它将1的第一个for循环添加到组合中(它使用DFS遍历树)。其次,该方法从元素2开始调用,传递原始列表(包含1)。从那里返回由元素12组成的配置。根据边缘的类型处理返回的列表(强制或可选,并相应地添加到原始列表)。在该特定情况下,列表应包含两个组合11,2。当第一个边缘下的子树(在这种情况下只有元素2)完成时,处理下一个子树(356)。

以下内容不再重要,请在水平栏下查看。

但目前我对递归感到困惑,因为当我调用它时,返回的combinations列表为空。我相信这与治疗叶子的方式有关。因为目前这些都没有得到妥善处理(参见上面的粗体文字)。 有人有想法吗?当然,如果有必要,我会尝试更详细地解释这个问题,并感谢你在这个问题中提出的想法。

对原始问题的规范:让我们假设算法一般有效(我已经多次检查过)。所以我想我对Java的理解在程序的某些部分是不正确的。两个人认为可能很有趣:

  • 在我打开所有编译器警告(感谢Prune)之后,我们留下了The parameter configurations should not be assigned,并且它显示Configure problem severitycombinations = childCombinations;。这可能是个问题吗?
  • 是否可能直接传递给函数的变量combinations在第二个/第三个/ ...递归调用中而不是在返回后更改?

目前程序产生的输出返回以下组合(显然不对):

  • [1,2,3,3,5,5,6,6,4,4,7,7]
  • [1,2,3,3,5,5,6,6,4,4,7,7]
  • [1,2,3,3,5,5,6,6,4,4,7,7]
  • [1,2,3,3,5,5,6,6,4,4,7,7]

感谢您的时间和精力!

2 个答案:

答案 0 :(得分:0)

我认为你在开始时将节点添加到列表中的每个组合的逻辑是错误的,应该在之后完成。

正确的算法可能如下所示:

  1. 使用根节点调用递归函数。返回组合列表。

  2. 如果是叶子节点,则返回仅包含该节点的组合列表。

  3. 对于每个边缘:使用子节点调用递归函数并保存结果。

  4. 创建强制子项的所有可能组合。例如。有两个强制性孩子,一个有11个组合,另一个有7个,你得到11 * 7 = 77个组合。您基本上构建了所​​有强制组合的交叉产品,这是更简单的步骤。如果节点没有强制子节点,则它是一个组合列表,其中包含一个空组合(稍后将向其添加当前节点)。

  5. 与可选子项的组合结合使用。例如。有两个额外的可选儿童,3种和5种组合,你得到77 + 77 * 3 + 77 * 5 + 77 * 3 * 5 = 1848种组合。在这里你可以选择是否采用一个可选的分支,因此它很快爆炸,有3个分支a,b,c和x强制组合,你得到x + ax + bx + cx + abx + acx + bcx + abcx(取和不取a,b或c的所有组合)。

  6. 将调用函数的节点添加到每个组合并返回结果。

答案 1 :(得分:0)

因为我无法清楚地解释问题,所以无法发布解决方案。现在我将发布解决方案。首先,破坏惊喜:算法是正确的,因为我理解它,但在整个递归过程中相同的combinations变量被改变。因此,同样的组合产生了四次。

解决方案是使用clone方法,因此将List更改为LinkedList

private LinkedList<Combination> recursiveConfiguration(LinkedList<Combination> combinations){

    for(Combination c: combinations){
        c.add(this);
    }

    // Iterate through all edges of the current node
    for(Edge e: getEdges()){

        int type = e.getType();     

        if((type == 1) || (type == 2)){
            // If type is mandatory or optional.

            // In this case getChildren always returns only one feature.
            LinkedList<Combination> copyConfig = new LinkedList<>();

            for(Combination c: configurations){
                @SuppressWarnings("unchecked")
                LinkedList<FeatureNode3> copy = (LinkedList<FeatureNode3>) c.getFeatureList().clone();
                Combination config = new Combination();
                config.setFeatureList(copy);
                copyConfig.add(config);
            }

            FeatureNode3 child = e.getChildren().get(0);    
            LinkedList<Combination> childCombinations = child.recursiveConfiguration(copyConfig);

            if(type == 1){
                // If type is mandatory
                combinations = childCombinations;
            }else{
                // If type is optional
                combinations.addAll(childCombinations);
            }   
        }
    }           
    return combinations;
}

感谢您的时间和建议!