简体   繁体   English

最有效的算法,为长凳生成随机座位表?

[英]Most efficient algorithm to generate a random seating chart for benches?

I'm writing an app for a family member who is a teacher. 我正在为一位教师的家庭成员编写应用程序。 She asked for an app to allow her to enter a bunch of kids, set their handedness, set who they can't sit next to, specify how many seats per bench, and then generate a random layout for the kids such that no left-handers sit to the right of a right-hander, and the kids that shouldn't sit next to each other are not adjacent on a bench. 她要求一个应用程序,允许她进入一群孩子,设定他们的惯用手,设置他们不能坐在旁边的人,指定每个工作台有多少个座位,然后为孩子们生成一个随机的布局,这样就没有了 - 汉德斯坐在右手边的右边,不应该坐在一起的孩子们不会坐在长椅上。

This isn't quite the same problem as a generic table seating algorithm because there are 2 ends to a bench, and because there is no "value" to the nodes to create any preferential groupings. 这与通用表座位算法的问题并不完全相同,因为一个工作台有2个端点,并且因为节点没有“值”来创建任何优先分组。

I decided to create a directed graph where the edges represent who can sit to the right of a given kid. 我决定创建一个有向图,其中边表示谁可以坐在给定孩子的右边。 Then I do a recursive DFS from every node without touching a node twice until I get a path where every node has been touched. 然后我从每个节点做一个递归DFS而不触摸节点两次,直到我得到一个触摸每个节点的路径。 One catch is that at the "end" of every bench, anyone can sit to their "right". 一个问题是,在每个工作台的“终点”,任何人都可以坐到他们的“正确”。

This algorithm seems to always work, which is nice. 这个算法似乎总是有效,这很好。 But the runtime seems to grow awfully once I get beyond say 10 kids on a single bench assuming the benches can seat say 20 kids. 但是,一旦我超过10个孩子在一个长凳上,假设长椅可以说20个孩子,那么运行时似乎会变得非常糟糕。 Am I doing something wrong, or is there some much better way to solve this? 我做错了什么,还是有更好的方法来解决这个问题? Java code follows. Java代码如下。

Edit: Sorry I didn't make this clear, but I want to achieve a RANDOM seating arrangement each time, such that the kids don't get stuck in the same place or on the same bench or next to the same kids. 编辑:对不起,我没有说清楚,但我希望每次都能实现RANDOM座位安排,这样孩子们就不会被困在同一个地方或同一个长凳上或者在同一个孩子旁边。 Also I've got my app running against this algorithm here: 此外,我的应用程序运行此算法:

http://kcraigie.com/sittychart http://kcraigie.com/sittychart

Currently I'm enforcing an upper limit of 1,000,000 node touches so that my server doesn't get hosed. 目前我正在强制执行1,000,000个节点触摸的上限,这样我的服务器就不会受到冲击。 You can see that the algorithm seems to scale properly until you set the seats per bench to 9 or so at which point it immediately becomes unwieldy. 您可以看到算法似乎正确缩放,直到您将每个工作台的座位设置为9左右,此时它立即变得难以处理。

private static class Person {
    private String m_name = null;
    private Handedness m_handedness = null;
    private Set<Person> m_nonadjacents = null;
}

private static class Node {
    private Person m_person = null;
    private List<Node> m_possibleRightNodes = null;
    private boolean m_isInPath = false;
}

private Stack<Node> generateSeatingArrangement() {
    // Generate randomized directed graph, start with all nodes as root nodes
    for(Person leftPerson: people.values()) {
        Node node = new Node(leftPerson);
        nodes.put(leftPerson, node);
    }
    // Create all edges based on constraints
    for(Node leftNode: nodes.values()) {
        List<Node> possibleRightNodes = new LinkedList<>();
        for(Node rightNode: nodes.values()) {
            Person leftPerson = leftNode.getPerson();
            Person rightPerson = rightNode.getPerson();
            if(leftNode==rightNode) {
                log.fine("Can't seat '" + leftPerson.getName() + "' next to himself");
                continue;
            }
            if(leftPerson.getHandedness()==Person.Handedness.RIGHT_HANDED &&
               rightPerson.getHandedness()==Person.Handedness.LEFT_HANDED) {
                log.fine("Can't seat right-handed '" + leftPerson.getName()
                         + "' to the left of left-handed '" + rightPerson.getName() + "'");
                continue;
            }
            if(leftPerson.getNonadjacents().contains(rightPerson)) {
                log.fine("Can't seat '" + leftPerson.getName() + "' next to '" + rightPerson.getName() + "'");
                continue;
            }
            if(rightPerson.getNonadjacents().contains(leftPerson)) {
                // TODO: This should be redundant but not enforcing right now...
                log.fine("Can't seat '" + rightPerson.getName() + "' next to '" + leftPerson.getName() + "'");
                continue;
            }
            log.fine("Can seat '" + leftPerson.getName() + "' to the left of '" + rightPerson.getName() + "'");
            possibleRightNodes.add(rightNode);
        }
        Collections.shuffle(possibleRightNodes);
        leftNode.setPossibleRightNodes(possibleRightNodes);
    }
    List<Node> nodes2 = new LinkedList<>(nodes.values());
    Collections.shuffle(nodes2);

    // Perform recursive graph traversal
    Stack<Node> pathStack = new Stack<>();
    for(Node node: nodes2) {
        TraversalStatistics stats = new TraversalStatistics();
        boolean isPathFound = depthFirstSearchRecur(numSeatsPerBench, nodes2, pathStack, node, stats);
        if(isPathFound) {
            break;
        }
        pathStack.clear();
    }
}

// The resursive DFS method
private boolean depthFirstSearchRecur(int numSeatsPerBench,
                                      List<Node> allNodes,
                                      Stack<Node> pathStack,
                                      Node node,
                                      TraversalStatistics stats) {
    stats.numNodesTouched++;
    if(node.isInPath()) {
        stats.numLeavesReached++;
        return false;
    }
    pathStack.push(node);
    node.setIsInPath(true);
    if(pathStack.size() >= allNodes.size()) {
        return true; // We win!
    }
    if(pathStack.size() % numSeatsPerBench == 0) {
        // "End" of a bench, anyone can "sit to the right of" me
        for(Node node2: allNodes) {
            if(node == node2) {
                // Can't sit next to myself
                continue;
            }
            if(depthFirstSearchRecur(numSeatsPerBench, allNodes, pathStack, node2, stats)) {
                return true;
            }
        }
    } else {
        for(Node node2: node.getPossibleRightNodes()) {
            if(depthFirstSearchRecur(numSeatsPerBench, allNodes, pathStack, node2, stats)) {
                return true;
            }
        }
    }
    pathStack.pop();
    node.setIsInPath(false);
    return false;
}

I think you don't need to generate a list of possible left Nodes for each node. 我认为您不需要为每个节点生成可能的左节点列表。 Instead, do it on the fly in an recursive algorithm with trackback. 相反,在带有引用的递归算法中即时执行。

On the first seat you seat the first node. 在第一个座位上,您可以坐在第一个节点上。 Search through the list of nodes for the first possible neighbour and seat him there. 搜索第一个可能的邻居的节点列表并将他安置在那里。 Repeat until the plan is complete or you reach a dead end. 重复直到计划完成或达到死胡同。 In that case, go back one seat and seat the next possible person there (eg. if it was Node 4 change it to Node 5). 在这种情况下,返回一个座位并将下一个可能的人员安置在那里(例如,如果节点4将其更改为节点5)。 Either try the next seat from there or, if not possible to find a next Node (eg the node there was already the last on the list) go back one step until there is a next node. 从那里尝试下一个座位,或者,如果不可能找到下一个节点(例如,列表中已经存在最后一个节点的节点),则返回一步,直到有下一个节点。

With this method, you should statistically have to compute n!/2 possibilities for a number of students n to find an answer. 使用这种方法,您应该在统计上必须为许多学生计算n!/ 2种可能性来找到答案。

PS: I hope you understand my descrition of the algorithm. PS:我希望你理解我对算法的描述。 If i had more time i would have made a diagram. 如果我有更多的时间,我会做一个图表。

I wouldn't automatically think graphs for this sort of problem. 我不会自动想到这类问题的图表。 Building the graph gives a complexity of O(n^2), and a DFS is O(V+E) (with V being verticies, E being edges, which will be less than O(n^2), so you're still looking at O(n^2)). 构建图形给出O(n ^ 2)的复杂度,DFS是O(V + E)(V是顶点,E是边缘,它将小于O(n ^ 2),所以你是还在看O(n ^ 2))。

If no left hander can be directly to the right of a right hander, then for a single bench, there's no way to order the kids so that any right hander is to the right at all of any left hander. 如果没有左撇子可以直接在右撇子的右边,那么对于一个替补席,没有办法命令孩子,以便任何右撇子在任何左撇子的右边都是右边的。 So your result is always going to look something like: 所以你的结果总是看起来像:

l l l l l r r r r r

You can prove this by induction. 你可以通过归纳证明这一点。 If the correct solution has a right hander anywhere to the left of a left hander, ie : 如果正确的解决方案在左撇子左侧的任何地方都有一个右撇子,即:

r ? l

Then there must be a kid '?' 然后必须有一个孩子'?' who is either left or right handed that doesn't break the rule. 谁是左手或右手不会破坏规则。 If the kid is right handed, it breaks it for the first left handed kid. 如果这个孩子是右撇子,它会让第一个左撇子小孩打破它。 If the kid is left handed, it breaks the rule for that kid. 如果孩子是左撇子,那就违反了那个孩子的规则。

So, what you need to do is sort your left and right handed kids, and look at who can and can't sit next to each other. 所以,你需要做的就是对你的左手和右手孩子进行排序,看看谁能够和不能坐在一起。 In reality, if the kids relationships aren't like an episode of the bold and the beautiful, and you don't have a crazy graph of who can and can't sit next to each other, you could probably use a greedy algorithm. 实际上,如果孩子们的关系不像大胆和美丽的一集,并且你没有一个关于谁能够和不能坐在一起的疯狂图表,你可能会使用一种贪婪的算法。 You would just start from the list of left handers, pulling out one kid at a time. 你只需从左撇子列表开始,一次拔出一个孩子。 If they won't sit next to the last one, then pull out the next, and try again. 如果他们不会坐在最后一个旁边,那么拉出下一个,再试一次。 This would give you a complexity of O(n), but you might not get a solution everytime. 这会给你一个O(n)的复杂性,但你可能不会每次都得到一个解决方案。

Alternatively, you could use a recursive algorithm to put together compatible groups of kids one pair at a time, and build up a solution that way. 或者,您可以使用递归算法将一对孩子的兼容小组放在一起,并以此方式构建解决方案。 This would obviously be a more complex algorithm, but you'd always get an answer. 这显然是一个更复杂的算法,但你总能找到答案。

If you've got more than one bench though, then this won't work at all. 如果你有不止一个板凳,那么这根本不起作用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM