我正在为一位教师的家庭成员编写应用程序。她要求一个应用程序,允许她进入一群孩子,设定他们的惯用手,设置他们不能坐在旁边的人,指定每个工作台有多少个座位,然后为孩子们生成一个随机的布局,这样就没有了 - 汉德斯坐在右撇子的右边,不应该坐在一起的孩子不会坐在板凳上。
这与通用表座位算法的问题不完全相同,因为一个工作台有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;
}
答案 0 :(得分:1)
我认为您不需要为每个节点生成可能的左侧节点列表。相反,在带有引用的递归算法中即时执行。
在第一个座位上,您可以坐下第一个节点。搜索第一个可能的邻居的节点列表并将他安置在那里。重复直到计划完成或达到死胡同。在这种情况下,返回一个座位并将下一个可能的人员安置在那里(例如,如果节点4将其更改为节点5)。从那里尝试下一个座位,或者,如果不可能找到下一个节点(例如,列表中已有最后一个节点),则返回一步,直到有下一个节点。
使用这种方法,你应该在统计上必须为许多学生计算n!/ 2种可能性来找到答案。
PS:我希望你理解我对算法的描述。如果我有更多的时间,我会做一个图表。
答案 1 :(得分:1)
我不会自动为这类问题思考图表。构建图形给出O(n ^ 2)的复杂度,DFS是O(V + E)(V是顶点,E是边缘,它将小于O(n ^ 2),所以你&#39 ;仍然看着O(n ^ 2))。
如果没有左撇子可以直接在右撇子的右边,那么对于一个替补席,没有办法命令孩子,以便任何右撇子在任何左撇子的右边都是右边的。所以你的结果总是看起来像:
l l l l l r r r r r
你可以通过归纳来证明这一点。如果正确的解决方案在左撇子左边的任何地方有一个右撇子,即:
r ? l
然后必须有一个孩子?&#39;谁是左手或右手谁没有违反规则。如果这个孩子是右撇子,它会让第一个左撇子小孩打破它。如果孩子是左撇子,那就违反了那个孩子的规则。
所以,你需要做的就是对你的左手和右手孩子进行排序,看看谁能够和不能坐在一起。实际上,如果孩子们的关系不像一个大胆和美丽的情节,你就不会有一个疯狂的图表,说明谁可以和不能坐在一起,你可以可能使用贪心算法。你只需从左撇子列表开始,一次拔出一个孩子。如果他们不会坐在最后一个旁边,那么拉出下一个,再试一次。这会给你一个复杂的O(n),但你可能不会每次都得到一个解决方案。
或者,您可以使用递归算法将一对孩子的兼容小组放在一起,并以此方式构建解决方案。这显然是一个更复杂的算法,但你总能得到答案。
如果你有不止一个替补席,那么这根本不会起作用。