匈牙利算法【python,算法】

匈牙利算法的主要步骤如下:

  1. 记录原始矩阵为mat

  2. 对原始矩阵进行等效操作,操作方法如下:

    • 对于矩阵的每一行,找出最小值row_min_v,给这一行的每一个元素减去row_min_v
    • 对于矩阵的每一列,找出最小值col_min_v,给这一列的每一个元素减去col_min_v
  3. 主算法:
    记录矩阵的维度为dim,标记 0 元素的个数为zero_cnt
    如果zero_cnt<dim:

    • 对 0 元素矩阵进行划线,如果得到的划线行列总数小于dim,则需要调整矩阵。
    • 划线
      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 中。
    • 调整矩阵:
      1. 在未划线的元素中,找到最小值。
      2. 对所有未划线的元素减去最小值
      3. 对划线交叉点的元素加上最小值

    如果zero_cnt=dim,在原矩阵中标记出算法选择的元素,即标记 0 元素的位置所对应的元素。

下面通过手撕代码实现了匈牙利算法,并与scipy库的算法进行对比,可以发现手动实现的算法与库函数实现是等效的。

import copy
from pprint import pprint
from typing import List

import numpy as np
from scipy.optimize import linear_sum_assignment


def hungarian(mat: List):
	"""匈牙利算法

	步骤:
	1. 将矩阵转换为 numpy 数组
	2. 矩阵的等级转换
       - 对于每一行,找到最小值,然后将每个元素减去最小值
       - 对于每一列,找到最小值,然后将每个元素减去最小值
	3. 对矩阵进行划线处理,得到划线的行和列小标
	   - 将矩阵转换为 bool 类型的矩阵,True 表示 0 元素,False 表示其他元素。
	   - 循环划取 0 ,直到 bool 矩阵中没有 0 为止。
	     1. 查找矩阵中含有最少 0 的行,对该行中的第一个 0 所在的行列进行 False 处理,并将这个 0 的行列位置添加到 0 元素列表。
	     2. 通过 0 元素列表得到没有 0 元素的行,划取该行。
	     3. 找到这行中包含 0 的列,对列进行划线。
	     4. 找到这列中包划圈的 0 元素,并对这个元素所在的行划线。
	     5. 重复 3-4 步骤,直到不满足划去条件为止。
	4. 如果划线行列总数小于矩阵的维度,则按照下面的方法调整矩阵:
	   - 在未划线的元素中,找到最小值。
	   - 对所有未划线的元素减去最小值
	   - 对划线交叉点的元素加上最小值
	   执行完成后,跳转到步骤 3。
	5. 如果划线行列总数等于矩阵的维度,按照如下步骤计算结果:
	   - 在原矩阵中标记出最优匹配。
	   - 标记的同时计算最优价值。
	:param mat: 原始矩阵
	:return: 最优矩阵,最优条件下的代价值
	"""
	# 保留原始矩阵
	orig_mat = copy.deepcopy(mat)
	# 转化为 np 矩阵
	mat = np.array(mat)
	# 求矩阵进行等价处理
	reduce_mat = reduce_func(mat)
	dim = mat.shape[0]
	zero_count = 0
	while zero_count < dim:
		select_pos, marked_rows, marked_cols = mark_matrix(reduce_mat)
		zero_count += len(select_pos)
		if zero_count < dim:
			adjust_matrix(reduce_mat, marked_rows, marked_cols)
	else:
		cost, cost_mat = optimize_matrix(orig_mat, select_pos)
		pprint(f"total cost is {cost}!")
		pprint(cost_mat)
		return cost, cost_mat


def mark_matrix(mat):
	"""
	模拟划线过程,具体步骤为:
	# 把所有零元素全部标记
	# 计算最小画线次数,即 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)
	【返回没有打勾的行,和打勾的列】
	"""

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

	# 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)

	# 记录被标记过的行和列(也就是划过线的行和列)
	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])

	# 找到没被标记过的行(即没有独立 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]):
				# 找到没被标记的行中,是否有没被标记的 0 元素(也就是被迫被划线经过的列)
				# 在没有独立 0 元素的行中,找到所含 0 元素的列,加入到 marked_cols 中
				if row_array[j] == True and j not in marked_cols:
					marked_cols.append(j)
					check_switch = True
		# 对所有 marked_cols 中,独立的 0 元素所在的行取出来加到 non_marked_row 中
		for row_num, col_num in marked_zero:
			# 前面标记的独立 0 元素出现在独立 0 元素所在的列上
			if col_num in marked_cols and row_num not in non_marked_row:
				non_marked_row.append(row_num)
				check_switch = True

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


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矩阵
	"""

	min_row = [99999, -1]

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

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

	# 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)

	# 未标记的元素 - 最小值
	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

	# 标记的行和列 相交的元素 + 最小值
	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


def optimize_matrix(cost_mat, select_pos):
	"""在原矩阵中标记最优选择,并计算最优价值"""
	optimizer_cost = 0
	for i, row in enumerate(cost_mat):
		for j, v in enumerate(row):
			if (i, j) in select_pos:
				optimizer_cost += v
				cost_mat[i][j] = f"{cost_mat[i][j]}T"
	return optimizer_cost, cost_mat


def reduce_func(cost_mat: np.ndarray) -> np.ndarray:
	"""行列统一减去最小的数

	:param mat: 原始矩阵
	:return: 修改之后的矩阵,它与原矩阵的最优解相同
	"""
	col_reduce = cost_mat - np.min(cost_mat, axis=1, keepdims=True)
	row_reduce = col_reduce - np.min(col_reduce, axis=0, keepdims=True)
	return row_reduce


def hungarian_by_third_lib(cost_mat):
	"""通过第三方库的匈牙利算法计算 """
	work_idx_ls, pokeman_idx_ls = linear_sum_assignment(cost_mat)
	cost = 0
	for work_idx, poken_idx in zip(work_idx_ls, pokeman_idx_ls):
		cost += cost_mat[work_idx][poken_idx]
		cost_mat[work_idx][poken_idx] = f"{cost_mat[work_idx][poken_idx]}T"
	pprint(f"total cost is {cost}!")
	pprint(cost_mat)


if __name__ == '__main__':
	mat = [[7, 6, 2, 11],
	       [6, 2, 1, 3],
	       [5, 6, 8, 9],
	       [6, 8, 5, 8]]
	hungarian(copy.deepcopy(mat))
	hungarian_by_third_lib(copy.deepcopy(mat))

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Outlook发送大文件的问题是什么?怎么解决?

Outlook不仅是一款电子邮件客户端&#xff0c;还包括日历、任务、笔记、联系人等功能&#xff0c;同时与Microsoft Office套件中的其他应用程序&#xff08;如Word、Excel、PowerPoint等&#xff09;集成紧密&#xff0c;方便用户在不同应用程序之间切换&#xff0c;提高工作效…

TC3xx NvM小细节解读

目录 1.FlsLoader Driver和FlsDmu Driver 2. FlsLoader小细节 3.小结 大家好&#xff0c;我是快乐的肌肉&#xff0c;今天聊聊TC3xx NvM相关硬件细节以及MCAL针对NvM的驱动。 1.FlsLoader Driver和FlsDmu Driver 在最开始做标定的时候&#xff0c;认为标定数据既然是数据&…

力扣双指针算法题目:复写零

1.题目 . - 力扣&#xff08;LeetCode&#xff09; 2.解题思路 本题要求就是对于一个数组顺序表&#xff0c;将表中的所有“0”元素都向后再写一遍&#xff0c;且我们还要保证此元素之后的元素不受到影响&#xff0c;且复写零之后此数组顺序表的总长度不可以改变&#xff0c;…

C#(asp.net)房屋租赁管理系统-计算机毕业设计源码64421

目 录 摘要 1 绪论 1.1 研究背景与意义 1.2开发现状 1.3论文结构与章节安排 2 房屋租赁管理系统分析 2.1 可行性分析 2.1.1 技术可行性分析 2.1.2 经济可行性分析 2.1.3 法律可行性分析 2.2 系统功能分析 2.2.1 功能性分析 2.2.2 非功能性分析 2.3 系统用例分析 …

如何利用好用便签提高工作效率?

在忙碌的工作中&#xff0c;我们经常需要记住许多琐碎的任务。如果这些任务被遗忘&#xff0c;可能会对我们的工作产生影响。这时&#xff0c;便签就成为了我们的得力助手。通过合理的使用和管理&#xff0c;便签不仅能帮助我们记住重要的事项&#xff0c;还能提高我们的工作效…

中科蓝讯AB5607E蓝牙5.4 低成本带插卡带U盘音箱方案

方案概述 中科蓝讯AB5607E蓝牙5.4 低成本带插卡带U盘音箱方案&#xff0c;我们已有成熟的方案&#xff0c;用户可以免开发&#xff08;零代码&#xff09;快速完成带插卡带U盘蓝牙音箱&#xff0c;提供原理图&#xff0c;PCB Layout指导。 方案优势 低成本&#xff0c;IC成本低…

【Linux进程】进程优先级 Linux 2.6内核进程的调度

前言 进程是资源分配的基本单位, 在OS中存在这很多的进程, 那么就必然存在着资源竞争的问题, 操作系统是如何进行资源分配的? 对于多个进程同时运行, 操作系统又是如何调度达到并发呢? 本文将以Linux kernel 2.6为例 , 向大家介绍进程在操作系统中 (OS) 的调度原理; 1. 进程优…

【开发工具-前端必备神器】WebStrom2024版-安装和使用(小白学习)

一、官方下载地址 Other Versions - WebStorm 选择适合自己电脑的下载 二、安装步骤 1、双击下载的exe安装 2、选择安装目录【建议不要安装在C盘下】 3、安装选项&#xff0c;可以全选 4一直点击下一步就行了 5.双击运行 安装遇到问题&#xff1a; 我是下错版本了&#xff0…

Motion Guidance: 扩散模型实现图像精确编辑的创新方法

在深度学习领域&#xff0c;扩散模型&#xff08;diffusion models&#xff09;因其能够根据文本描述生成高质量图像而备受关注。然而&#xff0c;这些模型在精确编辑图像中对象的布局、位置、姿态和形状方面仍存在挑战。本文提出了一种名为“运动引导”&#xff08;motion gui…

【LLM】一、利用ollama本地部署大模型

目录 前言 一、Ollama 简介 1、什么是Ollama 2、特点&#xff1a; 二、Windows部署 1.下载 2.安装 3.测试安装 4.模型部署&#xff1a; 5.注意 三、 Docker部署 1.docker安装 2.ollama镜像拉取 3.ollama运行容器 4.模型部署&#xff1a; 5.注意&#xff1a; 总结 前言…

【C++】哈希表 ---开散列版本的实现

你很自由 充满了无限可能 这是很棒的事 我衷心祈祷你可以相信自己 无悔地燃烧自己的人生 -- 东野圭吾 《解忧杂货店》 开散列版本的实现 1 前言2 开散列版本的实现2.1 节点设计2.2 框架搭建2.3 插入函数2.4 删除函数2.5 查找操作2.6 测试 Thanks♪(&#xff65;ω&#x…

OpenCV 灰度直方图及熵的计算

目录 一、概述 1.1灰度直方图 1.1.1灰度直方图的原理 1.1.2灰度直方图的应用 1.1.3直方图的评判标准 1.2熵 二、代码实现 三、实现效果 3.1直方图显示 3.2 熵的计算 一、概述 OpenCV中的灰度直方图是一个关键的工具&#xff0c;用于分析和理解图像的灰度分布情况。直…

Excel多表格合并

我这里一共有25张表格: 所有表的表头和格式都一样,但是内容不一样: 现在我要做的是把所有表格的内容合并到一起,研究了一下发现WPS的这项功能要开会员的,本来想用代码撸出来的,但是后来想想还是找其他办法,后来找到"易用宝"这个插件,这个插件可以从如下地址下载:ht…

图像处理中的二维傅里叶变换

图像处理中的二维傅里叶变换 问题来源是对彩色图像进行压缩时得出的傅里叶系数的图像如何解释&#xff0c;导入图片&#xff0c;转化为灰度图片&#xff1a; #彩色图片一般是由RGB组成&#xff0c;其实就是3个二维数组叠加而成&#xff0c;当RGB时&#xff0c;彩色图片就会变成…

【线性代数的本质】矩阵与线性变换

线性变化要满足两点性质&#xff1a; 直线&#xff08;连续的点&#xff09;在变换后还是直线。原点不变。 假设有坐标轴&#xff08;基底&#xff09; i ^ \widehat{i} i 和 j ^ \widehat{j} j ​&#xff1a; i ^ [ 1 0 ] , j ^ [ 0 1 ] \widehat{i}\begin{bmatrix} 1 \…

【leetcode】双指针算法题

文章目录 1.算法思想2.移动零3.复写零方法一方法二 4.快乐数5.盛水最多的容器方法一&#xff08;暴力求解&#xff09;方法二&#xff08;左右指针&#xff09; 6.有效三角形的个数方法一&#xff08;暴力求解&#xff09;方法二&#xff08;左右指针&#xff09; 7.两数之和8.…

ONLYOFFICE 8.1版本震撼来袭,让办公更高效、更智能

官网链接&#xff1a; 在线PDF查看器和转换器 | ONLYOFFICE 在线办公套件 | ONLYOFFICE 随着科技的不断发展&#xff0c;办公软件已经成为现代企业提高工作效率、实现信息共享的重要工具。在我国&#xff0c;一款名为ONLYOFFICE的在线办公套件受到了越来越多企业的青睐。今天…

Prompt-Free Diffusion: Taking “Text” out of Text-to-Image Diffusion Models

CVPR2024 SHI Labshttps://arxiv.org/pdf/2305.16223https://github.com/SHI-Labs/Prompt-Free-Diffusion 问题引入 在SD模型的基础之上&#xff0c;去掉text prompt&#xff0c;使用reference image作为生成图片语义的指导&#xff0c;optional structure image作为生成图片…

深入理解【 String类】

目录 1、String类的重要性 2、常用方法 2、1 字符串构造 2、2 String对象的比较 2、3 字符串查找 2、4字符转换 数值和字符串转换&#xff1a; 大小写转化&#xff1a; 字符串转数组&#xff1a; 格式转化&#xff1a; 2、5 字符串替换 2、6字符串拆分 2、7 字符串…

知名品牌因商标痛失市场:114家直营店山寨店7000多家!

奶茶知名品牌“鹿角巷”当年红遍大江南北&#xff0c;是最早的新茶饮品牌&#xff0c;但是当年商标注册存在问题&#xff0c;被同行奶茶品牌抢占了先机&#xff0c;发声明“对大陆商标注册细则不详&#xff0c;在商标注册过程中让假店钻了法律空档”&#xff0c;最夸张的时候全…