根据与谁兼容来匹配组中人员的算法

问题描述

我试图根据他们的兼容性在一组人中找到最大匹配。当我意识到我没有 2 个不同的组时,我正朝着二部图上的最大基数匹配迈进。

我在哪里:

我有他们的 ID 列表:[1,8,3,15,13,21]

我有一个名为 verify 的函数,它可以验证 2 个 ID 是否兼容(可能是奇数)。

然后我创建一个每个人的索引兼容的索引的图(字典):

    ids = [1,21]
    l = len(ids)
    matches = {}
    for x in range(l):
        matches.setdefault(x,[])
        for y in range(l):
            if x != y:
                if verify(ids[x],ids[y]):
                    matches[x].append(y)

这会产生:

{0: [3,4,5],1: [2,2: [1,3: [0,4: [0,1,3],5: [0,2,3]}

现在我不确定从这里去哪里,或者我是否应该转向另一个方向。

有人能指出我正确的方向吗?谢谢

解决方法

看起来您正在尝试解决 Stable marriage problem。我写了一个 Rosetta Code task,其中包含 Python 和其他语言的解决方案。

,

在我看来,您正在寻找图中的最大集团,如果您想在图中找到所有节点彼此兼容(连接)的最大子集。据我所知,这是 np-complete 问题,但也许我问错了你的问题。

,

我编写了一些适用于您的示例的代码:

from typing import List,Dict,Optional,Tuple

compat = {0: [3,4,5],1: [2,2: [1,3: [0,4: [0,1,3],5: [0,2,3]}

def match(compat: Dict[int,List[int]]) -> Optional[List[Tuple[int,int]]]:
    """Match compatible people."""
    c = sorted(compat.items())
    matched: Dict[int,int] = {}
    m = _match(c,matched)
    if m:
        m = {tuple(sorted([k,v])) for k,v in m.items()}
        m = sorted(m)
    return m

def _match(c,matched):
    """Match compatible people recursively."""
    if not c:
        return matched
    person,compat = c[0]
    if person in matched:
        return _match(c[1:],matched)
    else:
        for comp in compat:
            if not comp in matched:
                matched[person] = comp
                matched[comp] = person
                if _match(c[1:],matched):
                    return matched
                else:
                    del matched[person],matched[comp]
    return None

print(match(compat))     # [(0,3),(1,4),(2,5)]
,

这是解决我的代码的逻辑(没有花时间将我的代码转换为 SO 版本:

它来自这个答案: https://stackoverflow.com/a/3303557/12941578

鉴于我发布的邻接矩阵(基本上是一组边,但为什么要转换):

{0: [3,3]}

最小的顶点是 2: [1,5],因此您取 2,1 点并移除顶点。

然后删除 2,1 的所有版本。 (如果是顶点,则将其全部删除)

这给你留下:

{0: [3,3]} 这是 1 场比赛

重复逻辑(4,0),你得到:这是匹配 2

{3: [5],5: [3]}

最后重复,你得到你的第 3 场比赛。

我假设这是基于一种算法,但我不知道是哪一种。

与我为匹配创建所有选项然后查看哪个版本最大的第一个解决方案相比,这非常快 谢谢

,

该算法尝试使用迭代方法从输入中挑选最大对,并且通常在随机数据上更详尽的搜索模式中出现一对,但只需要一小部分时间来运行

compats = [{0: [3,3]},{0: [4,1],1: [4,0],2: [4],3: [4],3]}]
def _check(compat):
    """Check compatibilities are mutual."""
    compat2 = {k:set(v) for k,v in compat.items()}
    for p0,comp in compat2.items():
        for p1 in comp:
            assert p0 in compat2[p1],\
                f"{p0} compat with {p1} but {p1} not compat with {p0}?"
    every_item = set()
    for v in compat2.values():
        every_item |= v
    diffs = set(compat).symmetric_difference(every_item)
    assert not diffs,f"compat inner list items != compat keys: {diffs}"

def most_matched2(compat: Dict[int,int]]]:
    """Match the most of the compatible people,iterably."""
    def reorder(c_list: List[Tuple[Any,List[Any]]]) -> List[Tuple[Any,Any]]:
        """
        Reorder compatibles; most compat first,their individual compatibilities
        sorted least compat first.

        ( reorder([(0,[4,1]),0]),[4]),(3,(4,[0,3])])
            == [(4,[3,(0,[1,4]),[4])] )
        """
        # sort most number of compatible first
        c_list.sort(key=lambda x:(-len(x[1]),x))
        # sort compatibles least compatible first
        order = [x[0] for x in c_list]
        for person,comp in c_list:
            #print(person,comp)
            comp.sort(key=lambda x:-order.index(x))
        return c_list

    c = [(k,list(v)) for k,v in compat.items()]
    matches = set()
    while c:
        c = reorder(c)
        p0,p1 = c[0][0],c[0][1][0]
        matches.add(tuple(sorted((p0,p1))))
        p0p1_set = {p0,p1}
        c = [(p,remain) for p,comp in c
             if p not in p0p1_set and (remain := list(set(comp) - p0p1_set))]

    return sorted(matches)


for compat in compats:
    _check(compat)
    print(most_matched2(compat)) 
print()

# %% test gen
from itertools import combinations
from random import sample,randint
from collections import defaultdict
from time import monotonic
import pandas as pd


def _test_gen(n,k):
    possibles = list(set(c) for c in combinations(range(n),2))
    if type(k) in {int,float}:
        k = int(min(k,len(possibles)))
    pairs = []
    while len(pairs) < k and possibles:
        p = possibles.pop(randint(0,len(possibles) - 1))
        pairs.append(p)
        if randint(0,1):
            possibles = [x for x in possibles if not p.intersection(x)]
    compatibles = sorted(tuple(sorted(comp)) for comp in pairs)
    data = defaultdict(set)
    for p1,p2 in compatibles:
        data[p1].add(p2)
        data[p2].add(p1)
    data = {key: sorted(val - {key}) for key,val in data.items()}
    return data,[sorted(x) for x in pairs]

compat,pairs = _test_gen(4,3)

print("\n## Random tests.")
for n,k in [(4,5),(8,(20,40)]:
    compat,pairs = _test_gen(n,k)
    _check(compat)
    print(ans1 := most_matched2(compat))