我有一组学生(在一般性标题中称为项目)。在这些学生中,有些人因为吵闹而闻名。我们被告知一组“我讨厌j”这种形式的仇恨关系。 '我讨厌j'不暗示'我讨厌我'。我们应该以一种方式安排学生排成行(最前面的行编号为1),如果'我讨厌j'那么我应该排成一行严格少于编号j(换句话说:在j的行前面的某一行),这样我就不会在j处抛出任何东西(不允许回头)。什么是有效的算法来找到所需的最小行数(每行不需要相同数量的学生)?
我们将做出以下假设:
1)如果我们将其建模为有向图,则图中没有循环。最基本的周期是:如果'我讨厌j'是真的,'j讨厌我'是假的。因为否则,我认为排序将变得不可能。
2)小组中的每个学生至少都被另一个学生讨厌,或者至少讨厌另一个学生。当然,会有学生既被一些人讨厌又反过来讨厌其他学生。这意味着没有流浪学生不构成图表的一部分。
更新:我已经考虑用i - >构建有向图。 j如果'我讨厌j并进行拓扑排序。但是,如果我必须将所有学生排成一行,那么一般的拓扑排序会更适合。由于这里的行有一个变化,我试图找出如何将变化纳入拓扑排序,以便它给我我想要的东西。
当您回答时,请说明您的解决方案的复杂性。如果有人提供代码并且您不介意语言,那么我更喜欢Java,但当然任何其他语言都可以。
JFYI这不适用于任何类型的家庭作业(我不是学生btw :))。
答案 0 :(得分:3)
听起来我需要调查topological sorting。
答案 1 :(得分:1)
在假设#1中重要的是,该图中不得有任何循环。如果有任何循环,则无法解决此问题。
我首先要安排所有不讨厌后排其他学生的学生。然后你可以让那些讨厌这些学生的学生坐在下一行等等。
答案 2 :(得分:1)
这个问题基本上是解决longest path in a directed graph问题的另一种方法。行数实际上是路径中的节点数(边数+ 1)。
假设图表为acyclic,则解决方案为topological sort。 非循环比你的假设强一点1.不仅A - > B和B - > A无效。还有A - > B,B - > C,C - > A和任何长度的任何循环。
提示:问题是需要多少行,而不是哪一行的学生。这个问题的答案是最长路径的长度。
答案 3 :(得分:1)
这是来自项目管理理论(或调度理论,我不知道确切的术语)。那里的任务是关于排序作业(顶点是作业,弧是作业顺序关系)。
显然我们有一些没有循环的连接导向图。当且仅当a
讨厌b
时,从顶点a
到顶点b
存在弧。假设有一个源(没有传入弧)和目标(没有传出弧)顶点。如果不是这种情况,只需添加虚构的。现在我们想要找到从源到目的地的最长路径的长度(它将是行数 - 1,但请注意假想的椎骨)。
我们将顶点等级(r[v]
)定义为源与此顶点v
之间的最长路径中的弧数。显然我们想知道r[destination]
。查找排名的算法:
0) r_0[v] := 0 for all verteces v
repeat
t) r_t[end(j)] := max( r_{t-1}[end(j)], r_{t-1}[start(j)] + 1 ) for all arcs j
until for all arcs j r_{t+1}[end(j)] = r_t[end(j)] // i.e. no changes on this iteration
在每个步骤中,至少一个顶点增加其等级。因此,在此形式中,复杂性为 O(n^3)
。
顺便说一下,这个算法还为学生提供了行间分布。只是将学生按各自的级别分组。
编辑:具有相同想法的另一段代码。可能它更容易理解。
# Python
# V is a list of vertex indices, let it be something like V = range(N)
# source has index 0, destination has index N-1
# E is a list of edges, i.e. tuples of the form (start vertex, end vertex)
R = [0] * len(V)
do:
changes = False
for e in E:
if R[e[1]] < R[e[0]] + 1:
changes = True
R[e[1]] = R[e[0]] + 1
while changes
# The answer is derived from value of R[N-1]
当然这是最简单的实现。它可以进行优化,时间估计可以更好。
Edit2:明显优化 - 仅更新与上一步更新的椎骨相邻的椎骨。即引入一个队列,其队列的等级已更新。同样对于边缘存储,应该使用邻接列表。通过此类优化,复杂性将为 O(N^2)
。实际上,每个顶点最多可以出现在队列中rank
次。但顶点等级永远不会超过N
- 椎体数量。因此,算法步骤的总数不会超过 O(N^2)
。
答案 4 :(得分:1)
行数是有向图中最长路径的长度加1。作为极限情况,如果没有仇恨关系,每个人都可以放在同一行。
要分配行,请将第一行上没有被其他任何人讨厌的人放在第一行。这些是图表的“根”。如果N是从任何根到该人的最长路径的长度,则其他所有人都被放在第N + 1行(此路径的长度至少为1)。
简单的O(N ^ 3)算法如下:
S = set of students
for s in S: s.row = -1 # initialize row field
rownum = 0 # start from first row below
flag = true # when to finish
while (flag):
rownum = rownum + 1 # proceed to next row
flag = false
for s in S:
if (s.row != -1) continue # already allocated
ok = true
foreach q in S:
# Check if there is student q who will sit
# on this or later row who hates s
if ((q.row == -1 or q.row = rownum)
and s hated by q) ok = false; break
if (ok): # can put s here
s.row = rownum
flag = true
答案 5 :(得分:0)
简单回答= 1行。
将所有学生放在同一行。
实际上,这可能无法解决所述的问题 - 较小的行,而不是相等的行...
但我确信有更好的算法 - 看看非循环图。
答案 6 :(得分:0)
构建一个关系图,我讨厌j将有一个从i到j的有向边。所以最终结果是有向图。它应该是一个DAG,否则没有解决方案,因为它无法解决循环仇恨关系。
现在只需进行DFS搜索,并且在post节点回调期间,意味着一旦完成所有子节点的DFS,并且在从DFS调用返回到此节点之前,只需检查所有子节点的行号并分配这个节点的行号作为子行的最大行+ 1。如果有一个人不讨厌任何人,基本上没有邻接列表的节点只需给他第0行。
处理完所有节点后,反转行号。这应该很简单,因为这只是找到最大值并将行号分配为最大已分配的行号。
以下是示例代码。
postNodeCb( graph g, int node )
{
if ( /* No adj list */ )
row[ node ] = 0;
else
row[ node ] = max( row number of all children ) + 1;
}
main()
{
.
.
for ( int i = 0; i < NUM_VER; i++ )
if ( !visited[ i ] )
graphTraverseDfs( g, i );`enter code here`
.
.
}