匈牙利算法学习记录

news2024/9/25 21:21:57

匈牙利算法主要用来解决两个问题:求二分图的最大匹配数和最小点覆盖数。
匈牙利算法事实上有两个算法,分别解决指派问题和二分图最大匹配求解问题,此处算法指求解指派问题的匈牙利算法。

处理流程

方法一

在这里插入图片描述
具体如何实现呢?

代码实现

行列归约

主要执行的便是这个函数,在该函数中执行行规约,列规约操作,同时使用cover_zeros.calculate()来进行试指派,得出独立0元素所在的行与列

def calculate(self):
    """
    实施匈牙利算法的函数
    """
    result_matrix = self._cost_matrix.copy()

    # 步骤 1: 矩阵每一行减去本行的最小值
    for index, row in enumerate(result_matrix):
        result_matrix[index] -= row.min()

    # 步骤 2: 矩阵每一列减去本行的最小值
    for index, column in enumerate(result_matrix.T):
        result_matrix[:, index] -= column.min()
    # print('步骤2结果 ',result_matrix)
    # 步骤 3: 使用最少数量的划线覆盖矩阵中所有的0元素
    # 如果划线总数不等于矩阵的维度需要进行矩阵调整并重复循环此步骤
    total_covered = 0
    while total_covered < self._size:
        time.sleep(1)
        # print("---------------------------------------")
        # print('total_covered: ',total_covered)
        # print('result_matrix:',result_matrix)
        # 使用最少数量的划线覆盖矩阵中所有的0元素同时记录划线数量
        cover_zeros = CoverZeros(result_matrix)
        single_zero_pos_list = cover_zeros.calculate()
        covered_rows = cover_zeros.get_covered_rows()
        covered_columns = cover_zeros.get_covered_columns()
        total_covered = len(covered_rows) + len(covered_columns)

        # 如果划线总数不等于矩阵的维度需要进行矩阵调整(需要使用未覆盖处的最小元素)
        if total_covered < self._size:
            result_matrix = self._adjust_matrix_by_min_uncovered_num(result_matrix, covered_rows, covered_columns)
    # 元组形式结果对存放到列表
    self._results = single_zero_pos_list
    # 计算总期望结果
    value = 0
    for row, column in single_zero_pos_list:
        value += self._input_matrix[row, column]
    self._totalPotential = value

打勾划线与试指派

完成试指派操作,主要是为了完成打勾划线操作。其实际上完成的是原理中的步骤2操作,不过,其在代码实现上是按照下面的流程实现的。

方法二(与方法一完全相同)

在这里插入图片描述

其实执行上面的步骤与文章最开始的步骤2效果完全是一致的。如下图:

在这里插入图片描述

同时关于为何要进行未划线的减去最小值,则是为了增加0元素的数量,而交叉线的加上最小值其实也是做的初等列变换,让其不出现负值。
事实上步骤三按照第二种方式:未划线的行减去最小值,划线的列加上最小值来理解就更直观了。

在这里插入图片描述

def calculate(self):
    '''进行计算'''
    # 储存勾选的行和列
    ticked_row = []
    ticked_col = []
    marked_zeros = []
    # 1、试指派并标记独立零元素
    while True:
        # print('_zero_locations_copy',self._zero_locations_copy)
        # 循环直到所有零元素被处理(_zero_locations中没有true)
        if True not in self._zero_locations_copy:
            break
        self.row_scan(marked_zeros)

    # 2、无被标记0(独立零元素)的行打勾
    independent_zero_row_list = [pos[0] for pos in marked_zeros]
    ticked_row = list(set(range(self._shape[0])) - set(independent_zero_row_list))#的到未标记行
    # 重复3,4直到不能再打勾
    TICK_FLAG = True
    while TICK_FLAG:
        # print('ticked_row:',ticked_row,'   ticked_col:',ticked_col)
        TICK_FLAG = False
        # 3、对打勾的行中所含0元素的列打勾
        for row in ticked_row:
            # 找到此行
            row_array = self._zero_locations[row, :]
            # 找到此行中0元素的索引位置
            for i in range(len(row_array)):
                if row_array[i] == True and i not in ticked_col:
                    ticked_col.append(i)
                    TICK_FLAG = True

        # 4、对打勾的列中所含独立0元素的行打勾
        for row, col in marked_zeros:
            if col in ticked_col and row not in ticked_row:
                ticked_row.append(row)
                FLAG = True
    # 对打勾的列和没有打勾的行画画线
    self._covered_rows = list(set(range(self._shape[0])) - set(ticked_row))
    self._covered_columns = ticked_col

    return marked_zeros

在该方法内部有个row_scan方法,该方法是对行进行逐行扫描,选出独立零元素,marked_zeros即为选定的0元素。

 def row_scan(self, marked_zeros):
        '''扫描矩阵每一行,找到含0元素最少的行,对任意0元素标记(独立零元素),划去标记0元素(独立零元素)所在行和列存在的0元素'''
        min_row_zero_nums = [9999999, -1]
        for index, row in enumerate(self._zero_locations_copy):  # index为行号 找出改行最少的0元素
            row_zero_nums = collections.Counter(row)[True]
            if row_zero_nums < min_row_zero_nums[0] and row_zero_nums != 0:
                # 找最少0元素的行
                min_row_zero_nums = [row_zero_nums, index]#记录0的个数,哪行。
        # 最少0元素的行  min_row_zero_nums  记录0的个数,哪行
        row_min = self._zero_locations_copy[min_row_zero_nums[1], :]
        # 找到此行中任意一个0元素的索引位置即可
        row_indices, = np.where(row_min)#如两个0元素,分别为0,2,则返回前面的0即可。
        # 标记该0元素
        # print('row_min',row_min)
        marked_zeros.append((min_row_zero_nums[1], row_indices[0]))
        # 划去该0元素所在行和列存在的0元素
        # 因为被覆盖,所以把二值矩阵_zero_locations中相应的行列全部置为false,先将列变为false,再将行变为false
        self._zero_locations_copy[:, row_indices[0]] = np.array([False for _ in range(self._shape[0])])
        self._zero_locations_copy[min_row_zero_nums[1], :] = np.array([False for _ in range(self._shape[0])])

矩阵调整

最后便是试指派失败后的矩阵调整了

def _adjust_matrix_by_min_uncovered_num(self, result_matrix, covered_rows, covered_columns):
    """计算未被覆盖元素中的最小值(m),未被覆盖元素减去最小值m,行列划线交叉处加上最小值m"""
    adjusted_matrix = result_matrix
    # 计算未被覆盖元素中的最小值(m)
    elements = []
    for row_index, row in enumerate(result_matrix):
        if row_index not in covered_rows:
            for index, element in enumerate(row):
                if index not in covered_columns:
                    elements.append(element)
    min_uncovered_num = min(elements)
    # print('min_uncovered_num:',min_uncovered_num)
    # 未被覆盖元素减去最小值m
    for row_index, row in enumerate(result_matrix):
        if row_index not in covered_rows:
            for index, element in enumerate(row):
                if index not in covered_columns:
                    adjusted_matrix[row_index, index] -= min_uncovered_num
    # print('未被覆盖元素减去最小值m',adjusted_matrix)

    # 行列划线交叉处加上最小值m
    for row_ in covered_rows:
        for col_ in covered_columns:
            # print((row_,col_))
            adjusted_matrix[row_, col_] += min_uncovered_num
    # print('行列划线交叉处加上最小值m',adjusted_matrix)
    return adjusted_matrix

最大匹配边

int M, N;            //M, N分别表示左、右侧集合的元素数量
int Map[MAXM][MAXN]; //邻接矩阵存图
int p[MAXN];         //记录当前右侧元素所对应的左侧元素
bool vis[MAXN];      //记录右侧元素是否已被访问过
bool match(int i)
{
    for (int j = 1; j <= N; ++j)
        if (Map[i][j] && !vis[j]) //有边且未访问
        {
            vis[j] = true;                 //记录状态为访问过
            if (p[j] == 0 || match(p[j])) //如果暂无匹配,或者原来匹配的左侧元素可以找到新的匹配
            {
                p[j] = i;    //当前左侧元素成为当前右侧元素的新匹配
                return true; //返回匹配成功
            }
        }
    return false; //循环结束,仍未找到匹配,返回匹配失败
}
int Hungarian()
{
    int cnt = 0;
    for (int i = 1; i <= M; ++i)
    {
        memset(vis, 0, sizeof(vis)); //重置vis数组
        if (match(i))
            cnt++;
    }
    return cnt;
}

补充

事实上,上面的指派问题实际上是求最小权重二分图匹配问题

在这里插入图片描述
经过转换:是不是很眼熟,这不就是二分图匹配问题吗,这里就转换成了最大匹配,同时这里的最大匹配也是完全匹配。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/430447.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据结构】- 初识数据结构之时间复杂度(上)

文章目录前言一、什么是数据结构二、什么是算法三、算法效率3.1如何衡量一个算法的好坏3.2算法复杂度四、时间复杂度4.1时间复杂度的概念4.2大O的渐进表示法4.3常见时间复杂度计算举例总结前言 努力不是为了和别人一较高下 而是为了让生活多一种可能 别让世俗淹没生活的浪漫和…

MySQL:基本常识介绍、操作数据库、操作数据库中的表、操作表中的数据(增删改查)、MySQL 函数

文章目录Day 02&#xff1a;一、常见的 SQL 语句二、基本常识1. 数据库的列类型2. 数据库的字段属性三、操作数据库1. 操作数据库2. 操作数据库中的表&#xff08;1&#xff09;创建表&#xff1a;CREAT&#xff08;2&#xff09;修改表&#xff1a;ALTER&#xff08;3&#xf…

肖 sir_就业课__014python讲解

python讲解 一、python梳理 1、python 数据类型有哪些&#xff1f; 字符、列表、元组、字典、集合 2、列表、元组、字典、集合的区别&#xff1f; 3、python中函数&#xff1f; &#xff08;1&#xff09;自定义函数 def 函数名&#xff08;&#xff09; &#xff08;2&#…

聊聊架构方案选择

大家好&#xff0c;我是易安&#xff01; 在完成备选方案设计后&#xff0c;如何挑选最终的方案是一个很大的挑战&#xff0c;因为每个备选方案都是可行的。但是&#xff0c;没有哪个备选方案是完美的&#xff0c;因为每个方案都存在一些缺点或风险。此外&#xff0c;评价备选方…

薅!无魔法无限量GPT-4安卓App安装包;Notion AI从入门到精通;最全大模型进展汇总;雇AI给我打零工 | ShowMeAI日报

&#x1f440;日报&周刊合集 | &#x1f3a1;生产力工具与行业应用大全 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; &#x1f916; 『大模型进展汇总 (持续更新至4月17日)』应该是最全总结了吧 ShowMeAI资料编号 No.T001 &#xff08;进入社群获取高清PDF文件&#x…

AI已经解锁自动化能力 | 颠覆商业模式和劳动力市场

AI已经解锁自动化能力&#xff0c;将颠覆商业模式和劳动力市场。目前AutoGPT的开源项目&#xff1a; BabyAGI、Auto-GPT、AgentGPT、TeenagerAGI、Jarvis。 AutoGPT原理&#xff1a; 3个GPT4协同合作&#xff0c;一个GPT4负责分解目标创建任务&#xff0c;另一个GPT4负责分配…

面试必问的CAS原理你会了吗?

目录 一、什么是CAS&#xff1f; 二、CAS 基本原理 三、CAS 在 Java 语言中的应用 四、CAS 的问题 1、典型 ABA 问题 2、自旋开销问题 3、只能保证单个变量的原子性 五、有态度的总结 在并发编程中我们都知道i操作是非线程安全的&#xff0c;这是因为 i操作不是原子操作…

Jmeter常用断言之XPath断言

一般情况下&#xff0c;使用响应断言和json断言即可满足绝大部分断言需求&#xff0c;Xpath断言主要适用于&#xff1a;返回的数据格式为html或xml。 XPath是W3C的一个标准。XPath是一种表达式语言&#xff0c;它使用路径表达式来选取 XML 文档中的节点或节点集。XPath断言和XP…

Linux中jar包的启动脚本解析及问题

搭建运行环境时&#xff0c;把jar包打好外&#xff0c;我们还需要一个启动脚本&#xff0c;新建一个文件start.sh,内容如下&#xff1a; ps -ef | grep dvmrms | grep -v grep | awk {print $2} | xargs kill -9nohup java -jar dvmrms.jar >/dev/null 2>&1 &sl…

leetcode876.链表的中间节点

个人主页&#xff1a;平行线也会相交 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 平行线也会相交 原创 收录于专栏【LeetCode】 目录题目链接解法1&#xff1a;快慢指针解题代码题目链接 题目链接 解法1&#xff1a;快慢指针 解法一&#xff1a;快慢指…

opencv实践项目-修改表格缺失轮廓

目录 1. 背景2. 修复步骤2.1 图像灰度化&#xff0c;并进行高斯模糊2.2 对图像进行阀值处理2.3 查找轮廓2.4 利用存储的值了解表格的位置2.5 提取所有的水平线和垂直线2.6 合并垂直和水平的两个模版 3. 完整代码 1. 背景 如果大家在输入图像时&#xff0c;看到的第二行中的单元…

Laravel使用JWT

开始安装jwt &#xff08;本次安装不建议直接在项目中安装及使用&#xff09; 1.composer 安装jwt composer require tymon/jwt-auth 1.0.0-rc.1 2.在config 文件夹的app.php 中注册服务提供者 providers > [Tymon\JWTAuth\Providers\LaravelServiceProvider::class, ]…

计算机网络考试复习——第一章 1.5 1.6

1.5 计算机网络的类别 1.5.1计算机网络的定义&#xff1a; 系统集合&#xff0c;连接起来&#xff0c;协议工作&#xff0c;资源共享 计算机网络主要是由一些通用的、可编程的硬件互连而成的&#xff0c;而这些硬件并非专门用来实现某一特定目的&#xff08;例如&#xff0…

【Linux问题处理】Aborted (core dumped)报错python

文章目录一、命令检查1.python执行py文件2.gdb执行py文件二、进程检查1.检查所有python程序2.使用gdb检查进程三、core文件检查1.开启core文件存储能力2.core文件存储位置3.gbd查看core文件首先需要在ubuntu系统安装gdb工具。 sudo apt-get install gdbgdb是c的工具&#xff0…

SSM框架整合流程与原理解读(附源码链接)

本文参考黑马教程&#xff0c;对 MyBatis、Spring、SpringMVC 三个框架进行逐步整合&#xff0c;并对整合后事务失效原因进行总结。 源码链接&#xff1a;https://download.csdn.net/download/weixin_43819566/87690821 文章目录 一、搭建整合环境1.1 整合项目说明1.2 整合的思…

通过KNN分类模型预测股票涨跌,然后与基准收益画图对比

目录 1 获取数据 2 特征工程&#xff1a;定义一个用于分类的函数 3 特征工程&#xff1a;生成训练数据 4 根据训练数据对分类模型进行拟合&#xff0c;并给出得分 5 使用训练完成的分类模型进行数据预测 6 定义几个有用的函数 7 生成基准收益和策略收益对比结果 记录一下…

排序算法——快速排序(C语言多种实现及其优化策略)

快速排序总述快速排序递归框架单趟快速排序**hoare法****挖坑法**前后指针法快排改进key的选取**随机选key****三数取中**小区间优化**面对多个重复数据时的乏力**总述 快速排序可以说是排序界的大哥的存在&#xff0c;在c库中的qsort和c库中的sort两个排序底层都是用快速排序…

常用运放电路总结记录

前言 上一篇文章我们复习了一下运放的基本知识&#xff0c;尽量的用简单的描述带大家去理解运算放大器&#xff1a; 带你理解运算放大器 对于运放的使用&#xff0c;存在着一些经典常用的应用电路&#xff0c;这个其实网络上已经有大量的文章做记录总结了&#xff0c;作为电…

【Elastic (ELK) Stack 实战教程】11、使用 ElastAlert 实现 ES 钉钉群日志告警

目录 一、ElastAlert 概述 二、安装 ElastAlert 2.1 安装依赖 2.2 安装 Python 环境 2.3 安装 ElastAlert 2.4 ElastAlert 配置文件 2.5 创建 ElastAlert 索引 2.6 测试告警配置是否正常 三、ElastAlert 集成钉钉 3.1 下载 ElastAlert 钉钉报警插件 3.2 创建钉钉机器…

【硬件外设使用】——can

【硬件外设使用】——can can基本概念can 通讯can使用方法pyb.can can可用的传感器 can基本概念 CAN是Controller Area Network的缩写&#xff0c;即控制器局域网。它是一种多主机串行通信协议&#xff0c;用于连接计算机、传感器、执行器和其他设备。 常用于汽车、工业自动化…