二分图的最大匹配---匈牙利算法

本文深入探讨了匈牙利算法,一种高效的二分图匹配算法。通过实例解释了增广路径的概念及其在算法中的作用,详细阐述了算法的实现原理,并提供了一段清晰的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  写在之前:更多二分图知识,请关注--->二分图知识导航篇

定义

  摘自百度百科:匈牙利算法(Hungarian method)是由匈牙利数学家Edmonds于1965年提出,因而得名。匈牙利算法是基于Hall定理中充分性证明的思想,它是二分图匹配最常见的算法,该算法的核心就是寻找增广径,它是一种用增广路径求二分图最大匹配的算法。

  根据介绍,匈牙利算法就是一个不断寻找增广路径的过程。既然这样,我们就得先讲讲在图里,什么叫增广路径了。

增广路

  顾名思义,增加的更宽广的路径,也就是增加一条走的更远的路径。

  假设我们一开始在图里找到了第一条路,设集合A里的点A连接了集合Y点B。然后再找集合A里下一个顶点C,发现他想要连接的顶点是点B,但被刚才点A占了,那就出现了矛盾。于是我们退而求其次,问问点A还有没别的点可以连,如果有点D,就好办了,请点A连点D吧,那点C就可以连点B了,路径就是:C-->B-->A-->D,形成了3条边C-B, B-A, A-D。

  •   一共有3条边,奇数,性质1
  •   起点是点C,和点A是同一集合X,终点是点D属于Y集合,且点C和点D是新点,即没匹配的点,性质2、5
  •   路径简化后为C-B-A-D,可见整条路上没有重复的点,且点是交替在X和Y集出现的,性质3,4
  •   第1边C-B,第3边A-D都是新出现的边,第2条边是已经记录过的边,性质6
  •   当我们把B-A删掉后,图上剩下的边就是C-B, A-D,这就是新形成的匹配子图,匹配数为2,性质7
    图1
    图1

        但是如果点A没有其他可连,那就只能看点C可不可以连其他未匹配的点,如果有点E,那就将点C连点E,然后继续寻找下一个匹配点;若X集内顶点全部匹配完成,或没有任何可匹配的点,也就是说新的增广路也找不到了,那就认为这个图最大匹配已经形成。

增广路性质

1. 有奇数条边 。
2. 起点在二分图的X边,终点在二分图的Y边 。
3. 路径上的点一定是一个在X边,一个在Y边,交错出现。 
4. 整条路径上没有重复的点 。
5. 起点和终点都是目前还没有配对的点,其他的点都已经出现在匹配子图中 。
6. 路径上的所有第奇数条边都是目前还没有进入目前的匹配子图的边,而所有第偶数条边都已经进入目前的匹配子图,奇数边比偶数边多一条边 。
7. 于是当我们把所有第奇数条边都加到匹配子图并把条偶数条边都删除,匹配数增加了1。

最大匹配

  最大匹配,取最大。意思是这个图里,能够达到的点对点匹配数最大的状态。就像上面说的,当再也找不到新的增广路或者已经匹配完所有可匹配的点后,就是匹配数最大的状态了。而有个词叫完美匹配,也就是图上任意一个点都成功匹配,那么就完美了,也就完全了,所以完美匹配一定是最大匹配。

基本匈牙利算法实现原理

  •   首先我们需要一个m,n用来记录x集和y集内顶点数,和一个最大顶点数MAX_N,还有一个最大匹配数sum;
  •   然后一个二维数组line[MAX_N][MAX_N],来记录最初哪些点存在关系,即以后可以选择连线的点;和一个变量k,用于记录有多少对这样的关系;
  •   再有一个数组boy[MAX_N]用来记录,Y集内的顶点被X集哪些顶点选择了,即Y集内顶点的状态,被谁选择;
  •   最后一个数组used[MAX_N]很有意思,它是在每轮X集内顶点进行选择时,用来记录Y集内顶点的状态,和boy不一样的是,boy主要是用来记录Y集顶点被谁选择,而used是表示Y集顶点是否可选。因为在对新的X集顶点进行选择时,之前已经搭配好的Y集将被视为可选状态,是随时可被拆散的。所以used数组在对下一个X集顶点进行匹配前,要全部初始化为0,而boy则不用。
  • 原理内核
        int x,y,sum; 
        scanf("%d %d %d",&k,&m,&n);   
        memset(line, 0, sizeof(line));
        memset(boy, 0, sizeof(boy));
        for(int i = 0; i < k; i++){  
            scanf("%d %d",&x,&y);
            line[x][y] = 1;    
        }
        sum = 0;
        for(int i = 1; i <= m; i++){
            memset(used, 0, sizeof(used));     
            if(found(i))   
              sum++;  
        }
        printf("%d\n",sum);
    bool found(int x){
        for(int i = 1; i <= n; i++){  
            if(line[x][i] && !used[i]){  
                used[i] = 1;   
                if(boy[i] == 0 || found(boy[i])){  
                    boy[i] = x;     
                    return 1;      
                }
            }
        }
        return 0;  
    }

    补充说明:在这里X集是出发集,Y集是被匹配集,出发集被匹配集的意思是:从集合A出发在集合B里找匹配点

下面请看一道例题

描述

RPG girls今天和大家一起去游乐场玩,终于可以坐上梦寐以求的过山车了。可是,过山车的每一排只有两个座位,而且还有条不成文的规矩,就是每个女生必须找个个男生做partner和她同坐。但是,每个女孩都有各自的想法,举个例子把,Rabbit只愿意和XHD或PQK做partner,Grass只愿意和linle或LL做partner,PrincessSnow愿意和水域浪子或伪酷儿做partner。考虑到经费问题,boss刘决定只让找到partner的人去坐过山车,其他的人,嘿嘿,就站在下面看着吧。聪明的Acmer,你可以帮忙算算最多有多少对组合可以坐上过山车吗?

Input

输入数据的第一行是三个整数K , M , N,分别表示可能的组合数目,女生的人数,男生的人数。0<K<=1000 
1<=N 和M<=500.接下来的K行,每行有两个数,分别表示女生Ai愿意和男生Bj做partner。最后一个0结束输入。

Output

对于每组数据,输出一个整数,表示可以坐上过山车的最多组合数。

Sample Input

6 3 3
1 1
1 2
1 3
2 1
2 3
3 1
0

Sample Output

3

解题思路

  这题就是很明显,跟二分图最大匹配概念一样的意思,于是我将在AC代码里更加细致的讲解匈牙利算法。

AC代码

#include <iostream>
#include <algorithm>
#include <string.h>
#include <stdio.h>
#include <math.h>
using namespace std;
const int N = 505;
int line[N][N];     //新建可搭配数组
int boy[N],used[N];    //新建男生配对数组,和搭配标记数组
int k,m,n;

bool found(int x){
    for(int i = 1; i <= n; i++){    //遍历所有男生
        if(line[x][i] && !used[i]){     //如果第x个女生和第i个男生有关系,并且男生还没被选,就进行选择
            used[i] = 1;    //第i个男生被标记已选,为了防止递归时,再次被选
            if(boy[i] == 0 || found(boy[i])){   //如果此男生没有被任何人选择过,或者说选择男生的那位女生还有其他可选的对象(DFS)
                boy[i] = x;     //将当前男生选给当前女生
                return 1;       //并返回1
            }
        }
    }
    return 0;   //如果没有选择的对象,就返回0
}

int main(){
    int x,y;
    while(scanf("%d",&k) && k){     //读入k,若k为0则结束循环
        scanf("%d %d",&m,&n);   //读入m,n,女生和男生的数量
        memset(line, 0, sizeof(line));
        memset(boy, 0, sizeof(boy));
        for(int i = 0; i < k; i++){    //读入可搭配的女生和男生
            scanf("%d %d",&x,&y);
            line[x][y] = 1;     //两者连线,标记为1
        }
        int sum = 0;    //最后可配对数量
        for(int i = 1; i <= m; i++){
            memset(used, 0, sizeof(used));      //每次配对时,将所有男生看作都还没确定搭配对象
            if(found(i))    //配对第i位女生
                sum++;  //如果成功,总数加1
        }
        printf("%d\n",sum);
    }
    return 0;
}

  此外最大匹配与最小覆盖集有很大的相交点,所以请关注二分图知识导航篇

 

<think>好的,我现在需要回答用户关于匈牙利匹配算法的实现和应用场景的问题。首先,我得回顾一下匈牙利算法的基本概念和原理,确保自己理解正确。匈牙利算法主要用于解决二分图最大匹配问题,这在数据关联、任务分配等场景中很常见。用户提到了多目标跟踪中的应用,比如引用[2]中的描述,将t帧和t-1帧的检测框进行匹配,这应该是匈牙利算法在目标跟踪中的典型应用。 接下来,我需要明确用户的问题重点:实现和应用场景。用户可能希望了解算法的具体步骤以及如何在代码中实现,同时想知道哪些领域会用到这个算法。我需要结合引用中的内容,尤其是引用[2]提到的Python实现和步骤,以及引用[3]中的二分图匹配原理,来构建回答的结构。 首先,算法的实现部分需要分步骤解释。根据引用[2],匈牙利算法的步骤包括矩阵处理、行列最小值减去、划线覆盖零、调整矩阵等。可能需要更详细地解释这些步骤,并考虑如何转化为代码,例如使用二维数组表示损失矩阵,逐步处理每一行和列,以及如何找到最优匹配。不过引用中的步骤描述可能比较简略,需要进一步细化。 然后,应用场景方面,用户提到了多目标跟踪(引用[2])、点云匹配(引用[1]),以及二分图匹配(引用[3])。我需要将这些例子整理出来,并补充其他可能的场景,如任务分配、资源调度等。需要确保每个应用场景都清晰说明算法的作用,例如在目标跟踪中如何关联不同帧的检测框,减少ID切换。 在编写回答时,要注意使用中文,并且数学表达式用$...$格式,比如损失矩阵中的元素用$C_{ij}$,公式需要正确使用LaTeX语法。例如,在损失矩阵中,每个元素代表匹配的代价,可以表示为$C_{ij}$,其中i和j分别来自两个集合的元素。 另外,用户要求生成相关问题,我需要根据回答内容提出几个相关的问题,比如算法的优化方法、与其他算法的比较、实际应用中的挑战等。这些问题应该能够帮助用户进一步深入学习。 现在需要检查是否有遗漏的要点,比如算法的复杂度、实际实现中的注意事项,或者具体的代码示例。虽然用户没有明确要求代码,但引用[2]提到了Python实现步骤,可能可以考虑提供一个简化的代码框架,但要注意用户提供的引用中并没有完整的代码,所以需要避免编造,或者仅描述步骤而不写具体代码。 最后,确保回答结构清晰,分点说明实现步骤和应用场景,引用正确来源,并在末尾添加相关问题。同时,检查是否符合系统级指令的要求,如行内公式的格式、独立公式是否用$$...$$,以及正确的中文表达。</think>### 匈牙利匹配算法实现与应用场景 #### 一、算法实现 匈牙利算法通过**最小化匹配损失**实现二分图最大匹配,核心步骤如下(以损失矩阵$C_{m \times n}$为例)[^2]: 1. **矩阵归一化** - **行归约**:每行元素减去该行最小值,例如第$i$行的最小值$r_i$,则$C_{ij} = C_{ij} - r_i$。 - **列归约**:每列元素减去该列最小值,例如第$j$列的最小值$c_j$,则$C_{ij} = C_{ij} - c_j$。 2. **覆盖零元素** 用最少数量的水平/垂直线覆盖所有零。若线条数等于矩阵维度,则已找到最优匹配;否则进入调整阶段。 3. **矩阵调整** - 找出未被覆盖的最小元素$d$。 - 未覆盖行元素减$d$,覆盖列元素加$d$,例如$C_{ij} = C_{ij} - d$(若行未覆盖)或$C_{ij} = C_{ij} + d$(若列覆盖)。 4. **逐行匹配** 从仅含单个零的行开始匹配,删除已匹配的行列,递归处理剩余矩阵。 #### 二、应用场景 1. **多目标跟踪** 在目标跟踪中,将当前帧检测框(集合$T$)与前一帧轨迹(集合$S$)匹配,通过损失矩阵(如IoU距离或外观特征距离)实现ID关联。例如: $$ \text{Loss}(S_i, T_j) = 1 - \text{IoU}(S_i, T_j) $$ 2. **点云匹配** 在自动驾驶中,将不同时刻的激光雷达点云簇进行时间序列匹配,统一动态目标的轨迹(如引用[1]中的红绿点云对齐)。 3. **任务分配** 在分布式计算中,将任务分配给服务器以最小化总耗时,例如$C_{ij}$表示任务$i$在服务器$j$的执行时间。 4. **资源调度** 例如医院将患者(集合$S$)与空闲床位(集合$T$)匹配,优先满足紧急病例。 #### 三、Python实现示例 ```python import numpy as np from scipy.optimize import linear_sum_assignment # 损失矩阵示例(IoU距离) cost_matrix = np.array([[0.2, 0.8], [0.6, 0.3]]) # 使用匈牙利算法找到最优匹配 row_ind, col_ind = linear_sum_assignment(cost_matrix) print("匹配结果:", list(zip(row_ind, col_ind))) # 输出:[(0,0), (1,1)] ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值