约束相互矛盾的物品的最佳排序

时间:2018-06-20 10:42:14

标签: algorithm sorting

我有一组测试用例,我想找到运行它们的最佳顺序。顺序约束为:

  • 硬:一个测试用例A可以产生另一个测试用例B所需的输出,因此A 必须B之前进行处理< / li>
  • 软件:测试用例可以从另一个继承输入数据。如果父测试用例失败,则几乎可以肯定子测试用例也会以相同的方式失败。为了使故障诊断更加容易,我们希望首先运行父级,因此当我们运行子级时,我们可以查询过去的结果,如果父级失败,则跳过该子级。即:如果测试用例B继承自A,则在A之前处理B将会是 nice (但绝不是必须的)

如果我们有这些约束集的 ,我们可以做一个topographical sort并用它来完成,但是将两者结合起来似乎会使事情变得更加棘手,因为它们可以相互矛盾。

我可以看到两种可能的解决方案:

双重排名

给每个项目2个等级:项目在硬约束树和软约束树中的深度。然后,我们可以基于这些等级进行排序-赋予硬约束等级的排序优先级,并在硬约束等级相等时比较软约束等级。

这肯定会给我们一个有效的顺序,但最终可能会导致很多不必要的软约束。例如,考虑项目ABCD,其中:

  • A 必须先于B
  • C 必须先于D
  • 如果B早于C
  • ,那将是

硬约束排名(A:1,B:2,C:1,D:2)表示CB之间的硬约束实际上并不存在,当我们遇到时,我们以A,C,B,D的顺序结束希望使用A,B,C,D

约束遍历

从项目L的集合中建立订单列表S

  1. A中删除任意项目S
  2. 递归地添加A中尚未存在的所有L硬约束的前任,并将它们从S中删除
  3. 以递归方式添加仍在A中的所有S的软约束前任,将它们从S中删除
  4. A添加到L
  5. 重复直到S为空。

认为这会产生一个有效的订单,该订单只会在必要时打破软约束(它确实适用于上面的示例),但是我不相信我们不能做得更好。

问题

找到一种可以完全满足一组约束同时最大程度地满足另一组约束的项目排序问题是否得到了接受?

2 个答案:

答案 0 :(得分:0)

根据您的严格约束,使用诸如http://rosettacode.org/wiki/Topological_sort#Python这样的程序进行拓扑排序,该程序输出可以按任何顺序应用的项目。

应用仅在硬约束排序以子顺序划分之后具有相同优先级的项目之间排序的所有软约束。

答案 1 :(得分:0)

CS stackoverflow上发布此问题后,我们发现这里要查找的是最大无环子图。这是一个NP难题,因此实际上并没有一个快速,最佳的解决方案。

我实现了这个图类,它提供了一个合理的解决方案:

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;

public class DirectedWeightedGraph<T> {

    private final Map<T, Node<T>> nodes = new HashMap<>();

    public DirectedWeightedGraph( Collection<T> values ) {
        values.forEach( v -> nodes.put( v, new Node<>( v ) ) );
    }

    /**
     * Adds edges to the graph
     * 
     * @param weight The weight of the edges
     * @param target A function from edge origin to target. Returns <code>null</code> for no edge.
     * @return <code>this</code>
     */
    public DirectedWeightedGraph<T> edge( int weight, UnaryOperator<T> target ) {
        nodes.forEach( ( o, n ) -> Optional.ofNullable( target.apply( o ) )
            .ifPresent( t -> n.edgeTo( nodes.get( t ), weight ) ) );
        return this;
    }

    /**
     * Removes cycles from the graph by removing the lowest-weight edge in each cycle
     * 
     * @return <code>this</code>
     */
    public DirectedWeightedGraph<T> removeCycles() {
        nodes.values().forEach( n -> n.removeCycles() );
        return this;
    }

    /**
     * @return The graph node values, in an order such that linked-<i>to</i> values come before
     *         linked-<i>from</i> values. Note that cycles in the graph will result in incorrect
     *         results
     * @see #removeCycles()
     */
    public List<T> order() {
        List<T> order = new ArrayList<>();
        Set<T> values = new HashSet<>( nodes.keySet() );
        while ( !values.isEmpty() ) {
            T t = values.iterator().next();
            nodes.get( t ).add( order, values );
        }
        return order;
    }

    @Override
    public String toString() {
        return nodes.values().stream()
            .map( n -> n.toString() )
            .sorted()
            .collect( Collectors.joining( "\n" ) );
    }

    private static class Node<T> {

        private enum State {
            /**
             * Node has not been visited yet
             */
            UNKNOWN,
            /**
             * Node is being explored
             */
            EXPLORING,
            /**
             * Node and all descendants are not part of a cycle
             */
            ACYCLIC
        }

        public final T value;
        private final Set<Edge<T>> edges = new HashSet<>();
        private State state = State.UNKNOWN;
        private Edge<T> lastExplored;

        public Node( T value ) {
            this.value = value;
        }

        public Node<T> edgeTo( Node<T> target, int weight ) {
            edges.add( new Edge<>( this, target, weight ) );
            return this;
        }

        public Node<T> remove( Edge<T> e ) {
            edges.remove( e );
            return this;
        }

        public void removeCycles() {
            if ( state == State.ACYCLIC ) {
                return;
            }
            else if ( state == State.UNKNOWN ) {
                // recurse!
                state = State.EXPLORING;
                Set<Edge<T>> iterate = new HashSet<>( edges );
                for ( Edge<T> e : iterate ) {
                    if ( edges.contains( e ) ) {
                        lastExplored = e;
                        e.to.removeCycles();
                    }
                }
                lastExplored = null;
                state = State.ACYCLIC;
            }
            else if ( state == State.EXPLORING ) {
                // we've been here before!

                // Trace through the lastExplored edges till we're back here again to find the minimum
                // weight edge
                Edge<T> minimum = lastExplored;
                Node<T> n = lastExplored.to;
                while ( n != this ) {
                    if ( minimum.weight > n.lastExplored.weight ) {
                        minimum = n.lastExplored;
                    }
                    n = n.lastExplored.to;
                }

                // delete the lightest edge in the cycle. Choosing the optimal edge to delete is the bit
                // that makes this problem NP-hard, so let's not bother worrying about it too much
                minimum.delete();
            }
        }

        /**
         * Adds the node's prerequisites (the nodes that it links to), then itself to the list
         * 
         * @param order The list to build
         * @param values The set of nodes not yet in the last
         */
        public void add( List<T> order, Set<T> values ) {
            if ( values.remove( value ) ) {
                edges.forEach( e -> e.to.add( order, values ) );
                order.add( value );
            }
        }

        @Override
        public String toString() {
            return value + edges.stream().map( e -> "\n\t" + e ).collect( Collectors.joining() );
        }
    }

    private static class Edge<T> {

        public final Node<T> from;
        public final Node<T> to;
        public final int weight;

        public Edge( Node<T> from, Node<T> to, int weight ) {
            this.from = from;
            this.to = to;
            this.weight = weight;
        }

        public void delete() {
            from.remove( this );
        }

        @Override
        public String toString() {
            return from.value + "-" + weight + "->" + to.value;
        }
    }
}

硬约束边缘的权重高于软约束的权重,因此在破坏图中的循环时,我们将始终删除软约束。