表座,使用什么算法?

时间:2016-12-17 03:19:35

标签: algorithm language-agnostic computer-science

所以我遇到了这个问题,我需要弄清楚哪种算法可以解决它。

鉴于一群人,并且考虑到谁不喜欢这个群体中的人,请在圆桌会议上为所有人安排一个座位,这样就不会有人坐在他们不喜欢的人旁边(如果可能的话)。

我无法弄清楚如何接近它。基本上我把它画成一个有向图,其中节点代表一个人和边缘从不喜欢某人的人到他们不喜欢的人。然后,您希望将节点排列成圆形,以使两个节点之间没有边缘相邻。然而,我找不到解决这类问题的算法。

2 个答案:

答案 0 :(得分:1)

为了使您的陈述更容易一些,假设您“反转”您的图表,以便当且仅当他们可以坐在彼此旁边时,您在两个客人之间有边缘。

现在,你要找的是一个循环(闭合步行),它只包含每个顶点一次。这称为哈密顿循环。一般来说它是NP难的(你的问题也是如此,因为哈密顿循环的任何实例都可以减少到你的问题),但在某些条件下,它更容易。

遗传算法(如JasonC提到的)可以解决大多数问题,整数线性编程也是一种选择,也就是Constraint编程。

答案 1 :(得分:0)

老实说,我会选择某种退火算法。基本上是:

  1. 从一套规则和初步安排开始。
  2. 进行随机更改,如果有所改进则保留。
  3. 重复直到找到解决方案。在经过一些阈值迭代次数后放弃。
  4. 考虑以下内容(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