匈牙利匹配

2023-11-17

不带权重的二分图匹配

算法核心

把冲突节点的原先匹配的左节点重新连接到它的未被匹配的右节点,不断递归找到空闲的右节点
如果不存在空闲右节点,则新的左节点不能连接到该冲突节点上,必须连接其他节点。

代码示例

def main():
    count = 0
    m, n, l = input().split(' ')
    m = eval(m)
    n = eval(n)
    l = eval(l)
    map = {}                          # 记录每个左节点匹配的所有右节点
    owner = [-1 for i in range(n+1)]  # 记录右节点匹配的左节点

    # 读入每个节点的链接关系,存到map里
    for _ in range(l):
        x, y = input().split(' ')
        x = eval(x)
        y = eval(y)
        if x not in map:
            map.setdefault(x, [y])
        else:
            map[x].append(y)
    # 
    for left in map:
        used = [False for i in range(n+1)]  # 记录当前左节点遍历过的右节点
        if find(left,map,used,owner): # 表示该右节点是空闲节点,或者可以通过交换空闲出来
            count += 1

    print(count)

def find(left,map,used,owner):
    for right in map[left]:
        if not used[right]:
            used[right] = True  # 标记一下,以后的就不再选这个点。
            # owner[right] < 0 表示该右节点是空闲节点。
            # find(owner[right],map,used,owner) 为True 表示该节点可以通过交换空闲出来。
            if owner[right] < 0 or find(owner[right],map,used,owner):
                owner[right] = left
                return True
    return False

if __name__ == '__main__':
    main()

相关题目:P3386 【模板】二分图最大匹配

带权重的二分图匹配

算法步骤

  1. 对于一个代价矩阵,先把每一行减去本行最小值,每一列减去本行最小值。
  2. 最少的直线(横线或竖线)覆盖矩阵中的全部0元素。如果直线数量等于矩阵的秩,则找到最佳的指派方案;否则需要不断调整,直到直线数量等于矩阵的秩。
  3. 每个0元素即为独立的指派任务

算法核心

第二步是算法核心,主要包含两个问题

如何用最少的直线覆盖矩阵中的全部0元素

【本节图片来自超详细!!!匈牙利算法流程以及Python程序实现!!!通俗易懂

  1. 找到0元素最少的行,标记第一个,并且把该行和该列都标记,直到没有0元素
    在这里插入图片描述

  2. 对没有独立0元素的行打勾;对打勾的行所含0元素的列打勾;对所有打勾的列中所含独立0元素的行打勾。
    在这里插入图片描述

如何调整矩阵

【本节图片来自匈牙利算法原理与Python实现

  1. 在上一步中划过的线记为marked_rowsmarked_cols,找到划线之外的元素最小值
    在这里插入图片描述

  2. 划线之外的元素 - 最小值
    在这里插入图片描述

  3. marked_rowsmarked_cols重叠的元素 + 最小值
    在这里插入图片描述

返回调整后的矩阵

代码实例

【代码来自于匈牙利算法原理与Python实现】在此基础上添加中文注释以方便理解。

import numpy as np

def min_zero_row(zero_mat, mark_zero):
    '''
        1)找到零元素最少的行,以及该行第一个零元素,记录其坐标(min_row[1], zero_index)
        2)将该元素的行和列全部赋为False
    :param zero_mat:  Bool矩阵
    :param mark_zero: 存储标记的0元素的list
    :return: 没有返回值,直接修改bool矩阵
    '''
    '''
    The function can be splitted into two steps:
    #1 The function is used to find the row which containing the fewest 0.
    #2 Select the zero number on the row, and then marked the element corresponding row and column as False
    '''

    # Find the row
    min_row = [99999, -1]

    # 找到零元素最少的行,记为min_row= [0元素个数, 行号]
    for row_num in range(zero_mat.shape[0]):
        if np.sum(zero_mat[row_num] == True) > 0 and min_row[0] > np.sum(zero_mat[row_num] == True):
            min_row = [np.sum(zero_mat[row_num] == True), row_num]

    # Marked the specific row and column as False
    # np.where()返回零元素最少的行中,第一个零元素的下标
    zero_index = np.where(zero_mat[min_row[1]] == True)[0][0]
    # 存储标记0的位置
    mark_zero.append((min_row[1], zero_index))
    # 该标记0元素的这一行和这一列全部置为False
    zero_mat[min_row[1], :] = False
    zero_mat[:, zero_index] = False

def mark_matrix(mat):
    '''
    模拟划线过程,具体步骤为
    # 2-1 把所有零元素全部标记
    # 2-2 计算最小画线次数,即marked_rows为划横线,marked_cols为划竖线
        1)标记不包含被标记的0元素的行,并在non_marked_row中存储行索引;
        2)搜索non_marked_row元素,并找出相应列中是否有未标记的0元素;【找出未标记的独立0元素所在的行,加到non_marked_row,*打勾*】
        3)将列索引存储在marked_cols中;【上述行中把独立0元素包含的列都marked,*打勾*,这是之后要划的竖线】
        4)比较存储在marked_zero和marked_cols中的列索引;【4、5步是找出(3)中划的列线包括的marked_0元素,把这行加到non_marked_row,*打勾*】
        5)如果存在一个匹配的列索引,那么相应的行索引就会被保存到non_marked_rows中;
        6)接下来,不在non_marked_row中的行索引被保存在marked_rows中
    :param mat:
    :return: (marked_zero, marked_rows, marked_cols)
    【返回没有打勾的行,和打勾的列】
    '''


    # Transform the matrix to boolean matrix(0 = True, others = False)
    # 原矩阵中元素为0的地方标记为True,其他都为False
    cur_mat = mat
    zero_bool_mat = (cur_mat == 0)
    zero_bool_mat_copy = zero_bool_mat.copy()

    # Recording possible answer positions by marked_zero
    # marked_zero 记录了标记0的位置,按顺序存储
    marked_zero = []
    # 模拟划线过程
    while (True in zero_bool_mat_copy):
        # 每执行一次min_zero_row()函数
        # 就找到零元素最少的那一行,找到该行第一个零元素
        # 将这个零元素的行和列全部置为False
        # 直到所有零元素都被标记过
        min_zero_row(zero_bool_mat_copy, marked_zero)

    # Recording the row and column indexes seperately.
    # 记录被标记过的行和列(也就是划过线的行和列)
    marked_zero_row = []
    marked_zero_col = []
    for i in range(len(marked_zero)):
        marked_zero_row.append(marked_zero[i][0])
        marked_zero_col.append(marked_zero[i][1])
    # step 2-2-1

    # 找到没被标记过的行(即没有独立0元素的行)
    non_marked_row = list(set(range(cur_mat.shape[0])) - set(marked_zero_row))

    marked_cols = []
    check_switch = True
    while check_switch:
        check_switch = False
        for i in range(len(non_marked_row)):
            row_array = zero_bool_mat[non_marked_row[i], :]
            for j in range(row_array.shape[0]):
                # step 2-2-2
                # 找到没被标记的行中,是否有没被标记的0元素(也就是被迫被划线经过的列)
                # 在没有独立0元素的行中,找到所含0元素的列,加入到marked_cols中
                if row_array[j] == True and j not in marked_cols:
                    # step 2-2-3
                    marked_cols.append(j)
                    check_switch = True
        # 对所有marked_cols中,独立的0元素所在的行取出来加到non_marked_row中
        for row_num, col_num in marked_zero:
            # step 2-2-4
            # 前面标记的独立0元素出现在独立0元素所在的列上
            if col_num in marked_cols and row_num not in non_marked_row:
                # step 2-2-5
                non_marked_row.append(row_num)
                check_switch = True

    # step 2-2-6
    marked_rows = list(set(range(mat.shape[0])) - set(non_marked_row))
    # 最后划线最少的方式是把打勾的列和没打勾的行划出来
    return (marked_zero, marked_rows, marked_cols)


def adjust_matrix(mat, cover_rows, cover_cols):
    '''
    对矩阵进行调整:具体做法为:
        1)找到未被标记的元素中的最小值
        2)未被标记的元素 - 最小值
        3)标记的行和列中相交的元素 + 最小值
    :param mat: 原先操作过的矩阵
    :param cover_rows:  标记的行
    :param cover_cols:  标记的列
    :return: 调整后的矩阵
    '''
    # 找到未被标记的行和列中的最小值
    cur_mat = mat
    non_zero_element = []

    # Step 4-1 Find the minimum value
    for row in range(len(cur_mat)):
        if row not in cover_rows:
            for i in range(len(cur_mat[row])):
                if i not in cover_cols:
                    non_zero_element.append(cur_mat[row][i])

    min_num = min(non_zero_element)

    # Step4-2  未标记的元素 - 最小值
    for row in range(len(cur_mat)):
        if row not in cover_rows:
            for i in range(len(cur_mat[row])):
                if i not in cover_cols:
                    cur_mat[row, i] -= min_num

    # Step 4-3 标记的行和列 相交的元素 + 最小值
    for row in range(len(cover_rows)):
        for col in range(len(cover_cols)):
            cur_mat[cover_rows[row], cover_cols[col]] = cur_mat[cover_rows[row], cover_cols[col]] + min_num

    return cur_mat

def ans_calculation(mat, pos):
    '''
    计算最小代价
    :param mat: 原成本矩阵
    :param pos: 指派的元素位置
    :return: 最小代价
    '''
    total = 0
    ans_mat = np.zeros((mat.shape[0], mat.shape[1]))
    for i in range(len(pos)):
        total += mat[pos[i][0], pos[i][1]]
        ans_mat[pos[i][0], pos[i][1]] = mat[pos[i][0], pos[i][1]]
    return total, ans_mat


def hungarian_algorithm(mat):
    dim = mat.shape[0]
    cur_mat = mat

    # Step 1 - Every column and every row subtract its internal minimum
    # 每一行-该行最小值,每一列-该列最小值
    for row_num in range(mat.shape[0]):
        cur_mat[row_num] = cur_mat[row_num] - np.min(cur_mat[row_num])

    for col_num in range(mat.shape[1]):
        cur_mat[:, col_num] = cur_mat[:, col_num] - np.min(cur_mat[:, col_num])

    zero_count = 0
    # 用横线或者竖线标记出所有为0的元素,用最少线的次数为zero_count
    # 当zero_count=矩阵的秩时,得到最终指派
    # 否则要调整当前矩阵
    while zero_count < dim:
        # Step 2 & 3
        ans_pos, marked_rows, marked_cols = mark_matrix(cur_mat)
        zero_count = len(marked_rows) + len(marked_cols)

        if zero_count < dim:
            cur_mat = adjust_matrix(cur_mat, marked_rows, marked_cols)

    return ans_pos

def main():
    # The matrix who you want to find the maximum sum
    cost_matrix = np.array([[7, 6, 2, 9, 2],
                            [6, 2, 1, 3, 9],
                            [5, 6, 8, 9, 5],
                            [6, 8, 5, 8, 6],
                            [9, 5, 6, 4, 7]])

    ans_pos = hungarian_algorithm(cost_matrix.copy())  # Get the element position.
    ans, ans_mat = ans_calculation(cost_matrix, ans_pos)  # Get the minimum or maximum value and corresponding matrix.
    # Show the result
    print(f"Linear Assignment problem result: {ans:.0f}\n{ans_mat}")

if __name__ == '__main__':
    main()

参考

  1. 匈牙利匹配算法_学习笔记_Python编程实现
  2. 超详细!!!匈牙利算法流程以及Python程序实现!!!通俗易懂
  3. 匈牙利算法原理与Python实现
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

匈牙利匹配 的相关文章

随机推荐

  • 1361: [蓝桥杯2018初赛]乘积尾零

    蓝桥杯2018省赛A组第3题 题目链接http oj ecustacm cn problem php id 1361 思路 找出2 5的个数 取最小的即可 include
  • Redis五大数据类型使用——String

    1 String 字符串 添加 查询 追加 获取长度 判断是否存在的操作 C Users 12559 gt redis cli exe 启动redis 127 0 0 1 6379 gt set name kobe 插入一个key为 nam
  • [附源码]java毕业设计高校学生疫情防控信息管理系统

    项目运行 环境配置 Jdk1 8 Tomcat7 0 Mysql HBuilderX Webstorm也行 Eclispe IntelliJ IDEA Eclispe MyEclispe Sts都支持 项目技术 SSM mybatis Ma
  • 【JavaWeb】Thymeleaf的简介与使用

    Thmeleaf MVC 为什么需要MVC 我们之前在书城项目第二阶段做登录的时候 曾经提出过优化登录失败后的处理 虽然说可以实现在登录失败之后跳转回到登录页面 并且展示失败信息 但是代码实在是太恶心了 根本没法维护 所以我们需要将视图展示
  • 【转】深度强化学习的加速方法

    原文地址 https www matools com blog 190533310 Accelerated methods for deep reinforcement learning 论文解读 深度强化学习一直以来都以智能体训练时间长
  • openGauss学习笔记-07 openGauss 语法

    文章目录 openGauss学习笔记 07 openGauss 语法 7 1 帮助 7 2 SQL语句格式 7 3 SQL语法 ABORT ALTER AUDIT POLICY ALTER DATA SOURCE ALTER DATABAS
  • Android实现图片点击放大

    第一步 查看大图 implementation com github SherlockGougou BigImageViewPager v4 6 1 1 第二步 在图片点击事件里调用 ImagePreview getInstance 上下文
  • 【mybatis】【mybatisPlus】

    springboot 下 mybatis mybatisPlus Mybatis的作用 配置数据源和mybatis的配置 Mybatis的作用 Mybatis就是帮助程序员将数据存取到数据库里面 MyBatis 是一个半自动化的ORM框架
  • 使用Java socket简单模拟HTTP服务器

    1 HTTP server 接收client端的http请求并将同级目录的root 返回 package httpDemo import java io InputStream import java io OutputStream imp
  • Electron 入门学习案例(electron 初体验)

    Electron 入门学习案例 electron 是桌面端的一个框架 可以把 html js css 封装成为一个 exe 或者 其他平台的应用程序 很好的实现了跨平台 并且开发效率很快 初始化环境 初始化 npm 环境使用命令npm in
  • 学习大数据spark——心得体会

    总结与体会 1 项目总结 本次项目实现了Spark 单机模式Python版的安装 介绍了与Spark编程有关的一些基本概念 特别对RDD的创建 转换和行动操作做了比较详细的说明 对从RDD 到DataFrame的实现进 行了案例训练 包括
  • PCB走线宽度和走过的电流对照表

    1 PCB走线宽度和走过的电流对照表 一般线路板厂家以OZ表示铜箔厚度 1OZ的厚度表示将1OZ重量的铜均匀铺在1平方英尺面积上达到的铜箔厚度 约为0 035mm 所以35um 50um 70um 对应的以oz为计量单位的厚度为1OZ 1
  • 解决SQL case when then else 在查询结果不存在时不生效的问题

    今天遇到一个问题 SQL 下的case when then else语句在查询结果不存在时不生效 今天解决了 顺便记录一下 为了方便的演示 先建个表Users Id Name Gender 1 白子画 0 2 花千骨 1 3 梅长苏 0 4
  • 财政收入影响因素分析

    目录 1 数据 2 代码 3 补充 1 数据 百度网盘链接 链接 https pan baidu com s 10I5FRbqSv0MGJ56SvmSTAg pwd 1234 提取码 1234 2 代码 coding utf 8 代码6 1
  • ArcMap连接表格(Join)相关问题整理

    表格连接是我们日常工作中ArcGIS常用的一项操作 常用Excel表格连接 但是在实际运用中 我们会遇到一些问题 这一般与我们使用的数据以及相关操作有关 在这里 我们根据实际经验 将一些常见问题与解决途径做一总结 1 表格无法连接 解决 检
  • python代码——批量压缩指定目录下的文件夹

    语言 python 3 用法 选择目录 对该目录下的文件夹分别压缩 生成同名压缩文件 并保存到该目录下 import os import shutil import zipfile from tkinter import Tk from t
  • k8s图形界面登录报错Failure

    k8s图形界面登录报错如下 kind Status apiVersion v1 metadata status Failure message forbidden User system anonymous cannot get path
  • echarts实现一个页面同时显示多个图表

    前言 因工作需要 老大要求给我一个JSON数据 用echarts 写一个option实现多个图表 折线图 饼图 关系图 展示 也就是说只要一个div dom对象 实现多个不同形状的图表展示 ps 前期没弄清老大意思 写了三个div来显示 尴
  • SpringToolSuite4中maven不能创建项目,创建后没有maven dependence,以及gradle创建后不能使用,更改阿里云仓库

    SpringToolSuite4中maven不能创建项目 创建后没有maven dependence gradle创建后不能使用 更改阿里云仓库 感慨一下 maven 配置指南 gradle 配置指南 感慨一下 其实这个问题很无语 搞了好几
  • 匈牙利匹配

    文章目录 不带权重的二分图匹配 算法核心 代码示例 带权重的二分图匹配 算法步骤 算法核心 如何用最少的直线覆盖矩阵中的全部0元素 如何调整矩阵 代码实例 参考 不带权重的二分图匹配 算法核心 把冲突节点的原先匹配的左节点重新连接到它的未被