当前位置: 首页 > 知识库问答 >
问题:

找到最有效的配对组

麹浩瀚
2023-03-14

我有一个团队,我希望每个人都能与团队中的其他人进行1:1的会谈。一个给定的人一次只能与另一个人见面,因此我想执行以下操作:

  1. 找到所有可能的配对组合
  2. 将配对分组为“轮”会议,每个人只能参加一轮会议,一轮会议应包含尽可能多的配对,以在最少的轮数中满足所有可能的配对组合

为了从期望的输入/输出方面演示问题,假设我有以下列表:

>>> people = ['Dave', 'Mary', 'Susan', 'John']

我想产生以下输出:

>>> for round in make_rounds(people):
>>>     print(round)
[('Dave', 'Mary'), ('Susan', 'John')]
[('Dave', 'Susan'), ('Mary', 'John')]
[('Dave', 'John'), ('Mary', 'Susan')]

如果我有奇数个人,那么我会期望这个结果:

>>> people = ['Dave', 'Mary', 'Susan']
>>> for round in make_rounds(people):
>>>     print(round)
[('Dave', 'Mary')]
[('Dave', 'Susan')]
[('Mary', 'Susan')]

这个问题的关键是,我需要我的解决方案是合理的。我已经编写了一些有效的代码,但是随着规模的增长,它的速度会呈指数级增长。我对编写性能算法了解不够,不知道我的代码是否效率低下,或者我是否仅仅受到问题参数的约束

步骤1很简单:我可以使用itertools.combinations获得所有可能的配对:

>>> from itertools import combinations
>>> people_pairs = set(combinations(people, 2))
>>> print(people_pairs)
{('Dave', 'Mary'), ('Dave', 'Susan'), ('Dave', 'John'), ('Mary', 'Susan'), ('Mary', 'John'), ('Susan', 'John')}

为了自己解决这些问题,我正在构建一个类似这样的循环:

  1. 创建一个空的列表
  2. 迭代使用上述组合方法计算的人对集合的副本
  3. 对于配对中的每个人,检查当前轮中是否存在已包含该个人的现有配对
  4. 如果已经有一对包含其中一个个体,则跳过此轮的配对。如果没有,则将配对添加到轮次中,并将配对从人员配对列表中删除
  5. 迭代所有人员对后,将轮添加到主roundslist
  6. 重新开始,因为人对现在只包含没有进入第一轮的人对

最终,这会产生期望的结果,并缩减我的人员对,直到没有剩余人员,所有轮次都计算出来。我已经看到这需要大量的迭代,但我不知道更好的方法。

这是我的密码:

from itertools import combinations

# test if person already exists in any pairing inside a round of pairs
def person_in_round(person, round):
    is_in_round = any(person in pair for pair in round)
    return is_in_round

def make_rounds(people):
    people_pairs = set(combinations(people, 2))
    # we will remove pairings from people_pairs whilst we build rounds, so loop as long as people_pairs is not empty
    html" target="_blank">while people_pairs:
        round = []
        # make a copy of the current state of people_pairs to iterate over safely
        for pair in set(people_pairs):
            if not person_in_round(pair[0], round) and not person_in_round(pair[1], round):
                round.append(pair)
                people_pairs.remove(pair)
        yield round

在列表大小为100-300的情况下,使用https://mycurvefit.com显示为1000人计算轮次可能需要大约100分钟。有没有更有效的方法

注意:我实际上并不是在组织一个1000人的会议:)这只是一个简单的例子,代表了我试图解决的匹配/组合问题。

共有3个答案

祁辰阳
2023-03-14

当您需要快速查找时,哈希/迪克斯是最好的选择。跟踪谁在每个回合中的法规,而不是列表,这样会快得多。

由于您正在学习算法,学习大O表示法将帮助您解决问题,了解哪些数据结构擅长于哪种操作也是关键。请参阅本指南:https://wiki.python.org/moin/TimeComplexity对于Python内置的时间复杂性。您将看到,检查列表中的项目是O(n),这意味着它与输入的大小成线性比例。因此,因为它是在一个循环中,所以最终得到O(n^2)或更糟的结果。对于dicts,查找通常是O(1),这意味着它与输入的大小无关。

此外,不要覆盖内置。我把改为round_

from itertools import combinations

# test if person already exists in any pairing inside a round of pairs
def person_in_round(person, people_dict):
    return people_dict.get(person, False)

def make_rounds(people):
    people_pairs = set(combinations(people, 2))
    people_in_round = {}
    # we will remove pairings from people_pairs whilst we build rounds, so loop as long as people_pairs is not empty
    while people_pairs:
        round_ = []
        people_dict = {}
        # make a copy of the current state of people_pairs to iterate over safely
        for pair in set(people_pairs):
            if not person_in_round(pair[0], people_dict) and not person_in_round(pair[1], people_dict):
                round_.append(pair)
                people_dict[pair[0]] = True
                people_dict[pair[1]] = True


                people_pairs.remove(pair)
        yield round_
柯良骏
2023-03-14

我只生成索引(因为我很难找到1000个名称=),但对于1000个数字,运行时间大约为4秒。

所有其他方法的主要问题是——它们使用对并使用对,有很多对,而且运行时间越来越长。我的方法不同于与人合作,而不是与人结对。我有一个dict(),它将此人映射到他必须会见的其他人的列表,这些列表最多有N个项目(而不是N^2,如成对)。因此节省了时间。

#!/usr/bin/env python

from itertools import combinations
from collections import defaultdict

pairs = combinations( range(6), 2 )

pdict = defaultdict(list)
for p in pairs :
    pdict[p[0]].append( p[1] )

while len(pdict) :
    busy = set()
    print '-----'
    for p0 in pdict :
        if p0 in busy : continue

        for p1 in pdict[p0] :
            if p1 in busy : continue

            pdict[p0].remove( p1 )
            busy.add(p0)
            busy.add(p1)
            print (p0, p1)

            break

    # remove empty entries
    pdict = { k : v for k,v in pdict.items() if len(v) > 0 }

'''
output:
-----
(0, 1)
(2, 3)
(4, 5)
-----
(0, 2)
(1, 3)
-----
(0, 3)
(1, 2)
-----
(0, 4)
(1, 5)
-----
(0, 5)
(1, 4)
-----
(2, 4)
(3, 5)
-----
(2, 5)
(3, 4)
'''
屠建本
2023-03-14

这是Wikipedia文章循环锦标赛中描述的算法的实现。

from itertools import cycle , islice, chain

def round_robin(iterable):
    items = list(iterable)
    if len(items) % 2 != 0:
        items.append(None)
    fixed = items[:1]
    cyclers = cycle(items[1:])
    rounds = len(items) - 1
    npairs = len(items) // 2
    return [
        list(zip(
            chain(fixed, islice(cyclers, npairs-1)),
            reversed(list(islice(cyclers, npairs)))
        ))
        for _ in range(rounds)
        for _ in [next(cyclers)]
    ]
 类似资料:
  • 最近在一次工作面试中,我被要求在给定约束条件下求两个数组的最大和。这个问题措辞不同,但归结起来就是我上面所说的。没有提到元素是不同的,或者数组是被排序的。 例子: 请注意,由于约束,这与在2个数组中查找第k个最大和的对不同。 一个蛮力解是取2个数组的笛卡尔积,计算每对的和,过滤掉大于7000的,排序,取相等的顶值,时间复杂度为O(n2),我们能做的更好吗?

  • 问题内容: 以下代码使用和将转换为JSON字符串。 有没有更快的方法? 有没有一种方法可以使用更少的内存? 问题答案: 由于JIT编译器只是分支和基本测试,因此它可能会使其变得非常快。您可以通过对回调进行HashMap查找来使其更加优雅,但我怀疑这样做会更快。至于记忆,这是非常苗条的。 我以某种方式怀疑此代码实际上是内存或性能的关键瓶颈。您有真正的理由尝试对其进行优化吗?

  • 我有麻烦,同时使用Java发送电子邮件到Zimbra邮件服务器运行异常。 这是我的类 我在我的“主”线程中得到了以下RuntimeException堆栈跟踪。

  • 我找不到任何关于数学交换和堆栈溢出的问题来回答这个特定问题。这是我发现的最相似的问题,但这个问题构造得太差,答案完全不充分。 我尝试过在谷歌上寻找无济于事。我确实发现了这一点,但这个公式似乎效率低下,因此不够。例如,如果我们取数字21... 现在想象一下找到远大于21的数字的共同因素,例如2,252和4,082...上述方法没有任何效率。 我想做的是找出最有效的方法来找到任何两个数字的所有公因数。

  • 对数组中的对象进行分组的最有效的方法是什么? 例如,给定对象数组: 我正在表中显示此信息。我想通过不同的方法进行分组,但我想求和这些值。 我使用underscore.js来实现它的groupby函数,这很有帮助,但并不能完成全部任务,因为我不希望它们“拆分”,而是“合并”,更像SQL方法。 我正在寻找的将能够总计特定的值(如果请求)。 因此,如果我执行了groupby,我希望收到: 如果我执行了分

  • 我试图在N大小的数组的k个元素中找到最小和次最小的元素(没有排序和最小/最大堆)。 使用传统的方法,首先从第< code>0个元素开始,在第< code>k个元素中找到最小的和第二小的元素,然后将起始点移动< code>1并重复该过程。但是它的复杂度是< code>O(Nk)。如果可能,我需要一个复杂度为< code>O(N)的解决方案。对此有什么建议吗? 在Jubobs的注释后编辑:例如,如果s