簡體   English   中英

最有效的算法,為長凳生成隨機座位表?

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

我正在為一位教師的家庭成員編寫應用程序。 她要求一個應用程序,允許她進入一群孩子,設定他們的慣用手,設置他們不能坐在旁邊的人,指定每個工作台有多少個座位,然后為孩子們生成一個隨機的布局,這樣就沒有了 - 漢德斯坐在右手邊的右邊,不應該坐在一起的孩子們不會坐在長椅上。

這與通用表座位算法的問題並不完全相同,因為一個工作台有2個端點,並且因為節點沒有“值”來創建任何優先分組。

我決定創建一個有向圖,其中邊表示誰可以坐在給定孩子的右邊。 然后我從每個節點做一個遞歸DFS而不觸摸節點兩次,直到我得到一個觸摸每個節點的路徑。 一個問題是,在每個工作台的“終點”,任何人都可以坐到他們的“正確”。

這個算法似乎總是有效,這很好。 但是,一旦我超過10個孩子在一個長凳上,假設長椅可以說20個孩子,那么運行時似乎會變得非常糟糕。 我做錯了什么,還是有更好的方法來解決這個問題? Java代碼如下。

編輯:對不起,我沒有說清楚,但我希望每次都能實現RANDOM座位安排,這樣孩子們就不會被困在同一個地方或同一個長凳上或者在同一個孩子旁邊。 此外,我的應用程序運行此算法:

http://kcraigie.com/sittychart

目前我正在強制執行1,000,000個節點觸摸的上限,這樣我的服務器就不會受到沖擊。 您可以看到算法似乎正確縮放,直到您將每個工作台的座位設置為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;
}

我認為您不需要為每個節點生成可能的左節點列表。 相反,在帶有引用的遞歸算法中即時執行。

在第一個座位上,您可以坐在第一個節點上。 搜索第一個可能的鄰居的節點列表並將他安置在那里。 重復直到計划完成或達到死胡同。 在這種情況下,返回一個座位並將下一個可能的人員安置在那里(例如,如果節點4將其更改為節點5)。 從那里嘗試下一個座位,或者,如果不可能找到下一個節點(例如,列表中已經存在最后一個節點的節點),則返回一步,直到有下一個節點。

使用這種方法,您應該在統計上必須為許多學生計算n!/ 2種可能性來找到答案。

PS:我希望你理解我對算法的描述。 如果我有更多的時間,我會做一個圖表。

我不會自動想到這類問題的圖表。 構建圖形給出O(n ^ 2)的復雜度,DFS是O(V + E)(V是頂點,E是邊緣,它將小於O(n ^ 2),所以你是還在看O(n ^ 2))。

如果沒有左撇子可以直接在右撇子的右邊,那么對於一個替補席,沒有辦法命令孩子,以便任何右撇子在任何左撇子的右邊都是右邊的。 所以你的結果總是看起來像:

l l l l l r r r r r

你可以通過歸納證明這一點。 如果正確的解決方案在左撇子左側的任何地方都有一個右撇子,即:

r ? l

然后必須有一個孩子'?' 誰是左手或右手不會破壞規則。 如果這個孩子是右撇子,它會讓第一個左撇子小孩打破它。 如果孩子是左撇子,那就違反了那個孩子的規則。

所以,你需要做的就是對你的左手和右手孩子進行排序,看看誰能夠和不能坐在一起。 實際上,如果孩子們的關系不像大膽和美麗的一集,並且你沒有一個關於誰能夠和不能坐在一起的瘋狂圖表,你可能會使用一種貪婪的算法。 你只需從左撇子列表開始,一次拔出一個孩子。 如果他們不會坐在最后一個旁邊,那么拉出下一個,再試一次。 這會給你一個O(n)的復雜性,但你可能不會每次都得到一個解決方案。

或者,您可以使用遞歸算法將一對孩子的兼容小組放在一起,並以此方式構建解決方案。 這顯然是一個更復雜的算法,但你總能找到答案。

如果你有不止一個板凳,那么這根本不起作用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM