听ghost讲那匈牙利算法的事情

具体的不用解释了吧,就是匈牙利算法,做二分图匹配用的。粘在这里只是为了防止自己电脑数据异常丢失……。

这是百度百科上匈牙利算法的pascal原版程序,只是改了改格式而已。百度上的是用邻接矩阵写的,过两天我写一个python-based邻接表版本的。矩阵版的时间复杂度为\(O(n^3)\),邻接表则变成了\(O(EN)\),在边的个数小的时候明显占优势。

#!pascal
program matching;
const
    max = 1000;
var
    map : array [1..max, 1..max] of boolean;
    match: array [1..max] of integer;
    chk : array [1..max] of boolean;
    m, n, i, t1, t2, ans,k: integer;

function dfs(p: integer): boolean;
var
    i, t: integer;
begin
    for i:=1 to k do
        if map[p, i] and not chk[ i ] then begin 
            chk[ i ] := true;
            //p是左边),i是右边
            //重点是这里在每次主循环中如果我保持当前的p-i对应暨让i的chk被p占用),并且
            //递归深搜地能给i的原matching找到一个新的对应并在最终一次递归的时候让最后的i
            //找到一个之前从来没有matching过的i并把最后的p分配给这个之前没有遇到过的i
            //
            //想像一个新人坐到一个椅子上他把这个椅子上原来坐着的人赶走了于是这个原来坐这儿
            //的人又找到他喜欢的另一把椅子如果另一把椅子上有人就把那个人再赶走以此类推
            //如果最后一个人还是能找到一把空椅子(matching[i] = 0)的话那就说明这堆椅子又
            //能多坐一个人了
            if (match[ i ] = 0) or dfs(match[ i ]) then begin
                match[ i ] := p;
                exit(true)
            end
        end;
    exit(false)
end;

begin
    readln(n, m);
    fillchar(map, sizeof(map), 0);
    k:=0;
    for i:=1 to m do begin
        readln(t1, t2);

        map[t1, t2] := true;
        if k<t2 then k:=t2
    end;

    fillchar(match, sizeof(match), 0);
    ans := 0;
    for i:=1 to n do begin
        fillchar(chk, sizeof(chk), 0);
        if dfs(i) then inc(ans)
    end;

    writeln(ans);
    for i:=1 to 1000 do
        if match[ i ] <> 0 then
            writeln(match[ i ], '-->', i)
end.

然后以下是时隔13年的邻接表:

#!python
# 邻接表样式

n, m = read()

pre = [None] * m
last = [] * n
other = [] * m
n_edge = 0
n_matching = 0

matching = [None] * n
visited = [False] * n

def is_extentable(p: int) -> bool:
    p_edge = last[p]
    while pre[p_edge] is not None:
        other_node = other[pre[p_edge]]
        if not visited[other_node]:
            visited[other_node] = True
            if matching[other_node] is not None or is_estentable(matching[other_node]):
                matching[other_node] = p
                return True
    return False

def add_edge(u: int, v: int) -> None:
    pre[n_edge] = last[u]
    last[u] = n_edge
    other[n_edge] = v

for _ in range(m):
    u, v = read()
    add_edge(u, v)

for i in range(n):
    if is_extentable(i):
        visited = [False] * n
        n_matching += 1

print(f'#matching: {n_matching}')
for v, u in sorted(zip(range(n), matching), key = lambda x: x[1], reverse=True):
    print(f'{u} -> {v}')

这篇最初是2009年9月3日写的,当时是正在准备高三最后一次竞赛,打算把关于竞赛所有学到的东西都写下来但是没有时间了。今年(2022)年年底又翻了出来,完善了一些注释,新写了邻接表的实现,修正了时间复杂度的表达方式(当然Pascal实现里面的一些小错误为了保持原汁原味我还是没有修改的:P)。回首往事如云卷云舒,感慨万千不知所言。


Written by Zhang, Zijian in 马放南山 on 六 17 十二月 2022.

Comments

comments powered by Disqus