所以我遇到了这个问题,我需要弄清楚哪种算法可以解决它。
鉴于一群人,并且考虑到谁不喜欢这个群体中的人,请在圆桌会议上为所有人安排一个座位,这样就不会有人坐在他们不喜欢的人旁边(如果可能的话)。
我无法弄清楚如何接近它。基本上我把它画成一个有向图,其中节点代表一个人和边缘从不喜欢某人的人到他们不喜欢的人。然后,您希望将节点排列成圆形,以使两个节点之间没有边缘相邻。然而,我找不到解决这类问题的算法。
答案 0 :(得分:1)
为了使您的陈述更容易一些,假设您“反转”您的图表,以便当且仅当他们可以坐在彼此旁边时,您在两个客人之间有边缘。
现在,你要找的是一个循环(闭合步行),它只包含每个顶点一次。这称为哈密顿循环。一般来说它是NP难的(你的问题也是如此,因为哈密顿循环的任何实例都可以减少到你的问题),但在某些条件下,它更容易。
遗传算法(如JasonC提到的)可以解决大多数问题,整数线性编程也是一种选择,也就是Constraint编程。
答案 1 :(得分:0)
老实说,我会选择某种退火算法。基本上是:
考虑以下内容(Python)。您要做的第一件事就是想出一种衡量表成功的方法。这里我们定义一个返回错误配对数的函数,其中:
enemies
:将一个人映射到他们不能坐在附近的人员名单的字典。table
:围绕桌子的名单形式的座位安排。第一个和最后一个元素是相邻的座位,因为它是一个圆桌。我们将把它用于一切。我们的目标是0。
def tablePenalty (enemies, table):
penalty = 0
for k, name in enumerate(table):
p = (k + len(table) - 1) % len(table)
n = (k + 1) % len(table)
if table[p] in enemies[name]:
penalty = penalty + 1
if table[n] in enemies[name]:
penalty = penalty + 1
return penalty
现在,我们已经实现了这一点,我们可以实现一个真正天真的搜索,它会不断地随机交换人员,直到找到满意的东西。这会将名称列表作为输入,并以座位顺序生成一个名称列表作为输出:
def findGoodSeating1 (names):
table = names[:]
while tablePenalty(enemies, table) > 0:
i, j = random.sample(range(0, len(table)), 2)
table[i], table[j] = table[j], table[i]
return table
这当然不是一个很好的算法。但它有时会起作用。如果没有找到解决方案(或者如果你运气不好),它会挂起,并且可以随机采取非常迂回的路线来达成解决方案。实际上,它基本上与搜索座位安排的所有扰动相同,除非它以随机顺序这样做,并且有时可以重新检查重复的安排。所以是的,不太好。
所以我们可以做出改进:只有改善座位安排才能保持交换:
def findGoodSeating2 (names):
table = names[:]
penalty = tablePenalty(enemies, table)
while penalty > 0:
newtable = table[:]
i, j = random.sample(range(0, len(table)), 2)
newtable[i], newtable[j] = newtable[j], newtable[i]
newpenalty = tablePenalty(enemies, newtable)
if newpenalty <= penalty: # Keep improvements.
table = newtable
penalty = newpenalty
return table
我们在这里使用<=
而不是<
。这对我们这里的简单算法来说非常重要。如果我们只使用<
,我们很容易陷入没有交换改善排列的情况。通过使用<=
,我们还可以保持不会伤害我们的掉线,这会提供一些额外的“踢”以帮助我们摆脱困境。这个算法运行得很好,你可能想停在这里。但是,您可以考虑进行另外一项更改:
def findGoodSeating3 (names):
table = names[:]
penalty = tablePenalty(enemies, table)
stuck = 0
while penalty > 0:
newtable = table[:]
i, j = random.sample(range(0, len(table)), 2)
newtable[i], newtable[j] = newtable[j], newtable[i]
newpenalty = tablePenalty(enemies, newtable)
stuck = stuck + 1
if newpenalty < penalty:
table = newtable
penalty = newpenalty
stuck = 0
elif stuck > 50: # An arbitrary threshold.
random.shuffle(table)
penalty = tablePenalty(enemies, table)
stuck = 0
return table
除了您注意到我们使用<
之外,这几乎与上述相同 - 我们严格保持改进。在这个变体中,“轻推”是由逻辑提供的,在50(你可以调整这个数字)交换后不会改善情况,我们只是随机地移动表并重新开始,希望从那里找到解决方案。 / p>
这是一个完整的演示:
import random;
# A list of names to test with.
names = [ 'Clarke', 'Octavia', 'Bellamy', 'Jaha', 'Murphy', 'Finn',
'Abby', 'Alie', 'Lexa', 'Kane', 'Lincoln' ]
# Build list of people each person can't sit next to.
enemies = {}
for name in names:
choices = [ x for x in names if x != name ]
enemies[name] = random.sample(choices, 3) # 3 enemies per person
print "%s: %s" % (name, enemies[name])
#-------------------------------------------------------------------
def tablePenalty (enemies, table):
penalty = 0
for k, name in enumerate(table):
p = (k + len(table) - 1) % len(table)
n = (k + 1) % len(table)
if table[p] in enemies[name]:
penalty = penalty + 1
if table[n] in enemies[name]:
penalty = penalty + 1
return penalty
def findGoodSeating1 (names):
table = names[:]
while tablePenalty(enemies, table) > 0:
i, j = random.sample(range(0, len(table)), 2)
table[i], table[j] = table[j], table[i]
return table
def findGoodSeating2 (names):
table = names[:]
penalty = tablePenalty(enemies, table)
while penalty > 0:
newtable = table[:]
i, j = random.sample(range(0, len(table)), 2)
newtable[i], newtable[j] = newtable[j], newtable[i]
newpenalty = tablePenalty(enemies, newtable)
if newpenalty <= penalty:
table = newtable
penalty = newpenalty
return table
def findGoodSeating3 (names):
table = names[:]
penalty = tablePenalty(enemies, table)
stuck = 0
while penalty > 0:
newtable = table[:]
i, j = random.sample(range(0, len(table)), 2)
newtable[i], newtable[j] = newtable[j], newtable[i]
newpenalty = tablePenalty(enemies, newtable)
stuck = stuck + 1
if newpenalty < penalty:
table = newtable
penalty = newpenalty
stuck = 0
elif stuck > 3 * len(table):
random.shuffle(table)
penalty = tablePenalty(enemies, table)
stuck = 0
return table
# Test them:
print findGoodSeating1(names)
print findGoodSeating2(names)
print findGoodSeating3(names)
示例输出:
Clarke: ['Bellamy', 'Lincoln', 'Octavia']
Octavia: ['Jaha', 'Abby', 'Bellamy']
Bellamy: ['Clarke', 'Abby', 'Alie']
Jaha: ['Finn', 'Lincoln', 'Alie']
Murphy: ['Octavia', 'Jaha', 'Lexa']
Finn: ['Lexa', 'Clarke', 'Alie']
Abby: ['Lexa', 'Clarke', 'Jaha']
Alie: ['Clarke', 'Kane', 'Lincoln']
Lexa: ['Murphy', 'Alie', 'Finn']
Kane: ['Lexa', 'Alie', 'Bellamy']
Lincoln: ['Murphy', 'Clarke', 'Octavia']
['Octavia', 'Lexa', 'Jaha', 'Bellamy', 'Murphy', 'Clarke', 'Kane', 'Finn', 'Lincoln', 'Abby', 'Alie']
['Clarke', 'Jaha', 'Bellamy', 'Lexa', 'Octavia', 'Alie', 'Abby', 'Finn', 'Lincoln', 'Kane', 'Murphy']
['Murphy', 'Clarke', 'Jaha', 'Lexa', 'Bellamy', 'Lincoln', 'Kane', 'Octavia', 'Finn', 'Abby', 'Alie']
由于算法是随机的,我们最终会得到3种不同但令人满意的解决方案。
现在有一些关于上述算法的重要说明:
无论如何,这并不是为了证明最佳算法是什么,只是为了给你一些关于如何开始使用模糊方法的想法。就个人而言,其中之一就是我会这样做的方式,因为虽然你当然可以找到一个基于图形的解决方案(甚至直接搜索 - 在这种情况下基本上类似于基于约束的解决方案 - 放置一个人,放置他们旁边的人,当你遇到一个糟糕的安排并尝试下一次扰动时),虽然如果你这样做肯定会非常快,但这只是 easy 。