首页 > 编程语言 >二分图匹配---Munkres算法(匈牙利算法)

二分图匹配---Munkres算法(匈牙利算法)

时间:2023-11-24 09:35:26浏览次数:36  
标签:Step Munkres void zero --- int 算法 row

在任务指派问题(如n项工作由n个人承担,每个人完成不同工作所花时间不同,那如何分配使得花费的时间最少)以及一些多目标检测任务中的数据关联部分(如一个目标有多个特征点,有多个目标时检测到的特征点属于哪一个目标的问题)常常会看到Munkres算法,这里从原理及实现上简单介绍一下Munkres算法。

一、Munkres算法介绍

Munkres算法也称为匈牙利算法,匈牙利算法起源于1955年 Kuhn 提出的指派问题解法,kuhn引用匈牙利数学家康尼格关于矩阵中0元素的定理:系数矩阵中独立0元素的最多个数等于能覆盖所有0元素的最少直线数,该解法称为匈牙利方法(Hungarian Method),1956年MERRILL M.FLOOD 给出了匈牙利方法的算法实现步骤,1957年James Munkres针对该方法做了改进,后来大家习惯叫匈牙利算法或Kuhn-Munkres(KM)算法或者Munkres算法,后续一些改进都沿用了这个名称,其主要用来解决分配问题。
如下图a,b,c,d四个工人完成p,q,r,s四项工作,矩阵C(i, j)表示每个工人完成对应一项工作所需的时间,经过求解后工人a对应工作q,b对应s,c对应r,d对应p,最少是23个单位的时间。

二、Munkres算法条件

  • 系数矩阵C为n阶方阵
  • 系数矩阵每个元素$C_{ij} \geq 0 $
  • 目标是求解最小值min

三、Munkres算法

接下来以实际例子来展开Munkres算法基本步骤。公司接到EJGR四项任务,如何给甲乙丙丁四名员工分配可以使得总花费时间最少?

构建成数学模型如下:

3.1 矩阵变换

  • 步骤1:系数矩阵的每行减去改行的最小值
  • 步骤2:系数矩阵的每列减去该列的最小值
    这个利用了指派问题的一个性质:系数矩阵C的一行(列)各元素减去该行(列)最小元素后得到的新系数矩阵B,B的最优解和C的最优解相同

那上述例子系数矩阵就变为:


3.2 试指派

经过第一步变换后,系数矩阵中出现了0元素,如果能找到n个独立的0元素,那系数矩阵将这些独立零元素所在位置记为1,其余设为0,即可得到问题的最优解。试指派可以分为以下几步:

  • 步骤1:从只有一个0元素的行开始,给这个0元素加圈\(\bigcirc\) (表示该行所代表的人只有一种任务可指派),然后划去该零元素所在列的其他0元素(表明该任务已指派),标记为\(\Phi\)
  • 步骤2:对只有一个0元素的列加圈 \(\bigcirc\) ,然后划去该行其他0元素,记为\(\Phi\)
  • 步骤3:反复进行步骤1和步骤2,直到所有的0元素都被加圈或者划去为止
  • 步骤4:如果经过步骤3之后还有未被加圈的0元素,且同一行至少有两个0元素(表明这个人可以指派多项任务),此时可以比较这些0元素对应列0元素较少的0元素加圈,然后划掉此列的0元素(表明选择性多的礼让选择性少的)进行试探,反复进行直到所有0元素都被加圈或者划掉
  • 步骤5:如果加圈的0元素个数m刚好等于系数矩阵的阶数n,则问题得到求解。如果m < n,则继续下一大步骤。

上面的例子经过试指派前面三个步骤得到:

此时加圈的0元素个数刚好也为4,所以


也就是说甲分配任务R,乙分配任务J,丙分配任务E,丁分配任务G,共花费28小时。

现在公司接到新的任务ABCDE,且新来了一个员工,其系数矩阵如下

经过步骤3.1和3.2后得到如下情况:

这时加圈的0元素个数为4 < 5(矩阵的阶数),此时就是试分配的步骤5,需要按3.3 作最少的直线覆盖所有零元素


3.3 作最少的直线覆盖所有零元素

这一步利用了前面提到的0元素的定理,其大体可以分为以下几步:

  • 步骤1: 对没有加圈\(\bigcirc\)零元素的行打上\(\surd\)
  • 步骤2: 对打\(\surd\)的行中含有标记为\(\Phi\)的列打上\(\surd\)
  • 步骤3: 再对打\(\surd\)的列中含有加圈\(\bigcirc\)零元素的行打上\(\surd\)
  • 步骤4: 重复步骤2和步骤3,直到不能新增\(\surd\)的行和列为止
  • 步骤5: 对没有打\(\surd\)的行画一横线,对打\(\surd\)的列画上一竖线,这就得到了覆盖所有零元素的最少直线数L
    如果L<n,则说明需要对系数矩阵进一步进行变换,转到3.4 对系数矩阵变换以增加0元素
    如果L==n 而m < n,则回到3.2的步骤4,另外选择一个零元素继续试探。

新的示例中系数矩阵经过3.3后变为:

此时直线数L=4 < 5(矩阵阶数),所以需要执行3.4


3.4 系数矩阵变换增加0元素

这一步的目的是增加0元素个数,分为以下几个步骤:

  • 步骤1: 在没有被直线覆盖的0元素中找出最小元素值min
  • 步骤2: 对打上\(\surd\)的行的各元素都减去min
  • 步骤3: 对打上\(\surd\)的列的各元素都加上min,这样可以保证原来的0元素不变。
  • 步骤4: 对变换后的系数矩阵执行3.2和3.3,直到找到最优解

在上述例子中,未被直线覆盖的元素中最小值为2,所以经过变换后系数矩阵变为:

再经过3.2得到

此时可以找到5个加圈的0元素,所以将其变为解矩阵:


四、Munkres实现

python实现可以参考:https://software.clapper.org/munkres/
C++实现可以参考: https://brc2.com/the-algorithm-workshop/
注意:在C++实现与前面提到的基本步骤有些差别(这样实现更快),参考链接里面步骤5涉及到了增广路径,这里简单讲一下其含义:

增广路径

其作用是从工人出发找到一条到任务零元素路径(表述可能不太准确),举个例子应该就明白了:
如下图所示,中间*号就是前面提到的加圈\(\bigcirc\),加一撇是前面提到的\(\Phi\),此时q任务分配给了a,p任务分配给了b,但此时c只能分配p,但p已经分配给了b。这时就出现了"礼让"的情况,就是选择多的让给选择少的(此时系数都是0,所以让了之后不会改变最终解),因为b还可以执行q任务,b将任务p让给了c,那就变成了p>c, 同理a把任务让给b,最后就变为了r>a, q>b, p>c。 这中间就出现了一条增广路径c>p>b>q>r。

C++ Code

参照后自己用C++实现了下,这里给出具体的代码放在本文最后附录。

五、衍生问题

前面提到了采用Munkres算法需要满足3个条件,但实际情况下可能并不满足,这里参考:https://blog.csdn.net/Wonz5130/article/details/80678410,给出了几种衍生问题。

5.1 如果不是求最小值,是求最大值Max如何处理?

找出系数矩阵中的最大值max,然后令系数矩阵变为max-系数矩阵各元素值,得到新系数矩阵,再按照正常匈牙利法即可求到。

5.2 系数矩阵不是方阵如何处理?

  • 工作 < 人数: 增加虚拟工作,虚拟工作对应的系数全设为0
  • 人数 < 工作: 增加虚拟工人,虚拟工人对应的系数全设为0

5.3 一个人可以做几件事

如果一个人可以做k件事,那就把这个人的系数复制k份,相当于增加了虚拟人数,如果不是方阵,再构建虚拟工作

5.4 某个人不能做某项工作

将问题转换为求最小值后,如果第i个人不能做第j项工作,则将系数\(C_{ij}\)设置为一个比系数矩阵中最大值大一些的数。

六、其他扩展

二分图最小分配问题还可以采用分枝定界法

  • 分支: 将一个问题不断细分为若干子问题, 之后逐个讨论子问题;
  • 定界: 分支很多的情况下, 需要讨论的情况也随之增多, 这里就需要定界, 决定在什么时候不在进行分支;

定界的作用是剪掉没有讨论意义的分支, 只讨论有意义的分支

具体内容可参考:
https://www.geeksforgeeks.org/job-assignment-problem-using-branch-and-bound/
http://www.cs.umsl.edu/~sanjiv/classes/cs5130/lectures/bb.pdf.

七、参考链接

https://brc2.com/the-algorithm-workshop/
https://software.clapper.org/munkres/
https://blog.csdn.net/Wonz5130/article/details/80678410
https://zhuanlan.zhihu.com/p/62981901
https://www.geeksforgeeks.org/hungarian-algorithm-assignment-problem-set-1-introduction/
https://www.geeksforgeeks.org/hungarian-algorithm-for-assignment-problem-set-2-implementation/.
钱颂迪 《运筹学》第四版第6章

八、附录

C++代码

class Munkres
{
public:
	Munkres(int nRow, int nCol);
	~Munkres();

	bool Build(const std::vector<std::vector<int> >& costs);

	float Compute(const std::vector<std::vector<int> >& costs, std::vector<int>& pairs);

private:
	void StepOne(int& step);
	void StepTwo(int& step);
	void StepThree(int& step);
	void StepFour(int& step);
	void StepFive(int& step);
	void StepSix(int& step);
	void StepSeven(std::vector<int>& pairs);
	void FindAZero(int& row, int& col);
	bool StarInRow(int row);
	void FindStarInRow(int row, int& col);
	void FindStarInCol(int col, int& row);
	void FindPrimeInRow(int row, int& col);
	void AugmentPath();
	void ClearCovers();
	void ErasePrimes();
	void FindSmallest(float& minVal);
	void ShowCostMatrix();
	void ShowMaskMatrix();


private:
	int nRow_;
	int nCol_;
	std::vector<std::vector<int>> costs_;    // nRow_ * nCol_
	std::vector<std::vector<int>> augPaths_;   // nRow_ * 2
	std::vector<char> rowCover_;   // 1 * nRow_
	std::vector<char> colCover_;   // 1 * nCol_
	std::vector<std::vector<char>> marks_;      // nRow_ * nCol_
	int pathRow0;
	int pathCol0;
	int pathCount;
};



/*
* Ref: https://brc2.com/the-algorithm-workshop/ 
* 
* The following 6-step algorithm is a modified form of the original Munkres’ Assignment Algorithm (sometimes referred to as the Hungarian Algorithm).  This algorithm describes to the manual manipulation of a two-dimensional matrix by starring and priming zeros and by covering and uncovering rows and columns.  This is because, at the time of publication (1957), few people had access to a computer and the algorithm was exercised by hand.
Step 0:  Create an nxm  matrix called the cost matrix in which each element represents the cost of assigning one of n workers to one of m jobs.  Rotate the matrix so that there are at least as many columns as rows and let k=min(n,m).

Step 1:  For each row of the matrix, find the smallest element and subtract it from every element in its row.  Go to Step 2.

Step 2:  Find a zero (Z) in the resulting matrix.  If there is no starred zero in its row or column, star Z. Repeat for each element in the matrix. Go to Step 3.

Step 3:  Cover each column containing a starred zero.  If K columns are covered, the starred zeros describe a complete set of unique assignments.  In this case, Go to DONE, otherwise, Go to Step 4.

Step 4:  Find a noncovered zero and prime it.  If there is no starred zero in the row containing this primed zero, Go to Step 5.  Otherwise, cover this row and uncover the column containing the starred zero. Continue in this manner until there are no uncovered zeros left. Save the smallest uncovered value and Go to Step 6.

Step 5:  Construct a series of alternating primed and starred zeros as follows.  Let Z0 represent the uncovered primed zero found in Step 4.  Let Z1 denote the starred zero in the column of Z0 (if any). Let Z2 denote the primed zero in the row of Z1 (there will always be one).  Continue until the series terminates at a primed zero that has no starred zero in its column.  Unstar each starred zero of the series, star each primed zero of the series, erase all primes and uncover every line in the matrix.  Return to Step 3.

Step 6:  Add the value found in Step 4 to every element of each covered row, and subtract it from every element of each uncovered column.  Return to Step 4 without altering any stars, primes, or covered lines.

DONE:  Assignment pairs are indicated by the positions of the starred zeros in the cost matrix.  If C(i,j) is a starred zero, then the element associated with row i is assigned to the element associated with column j.

Some of these descriptions require careful interpretation.  In Step 4, for example, the possible situations are, that there is a noncovered zero which get primed and if there is no starred zero in its row the program goes onto Step 5.  The other possible way out of Step 4 is that there are no noncovered zeros at all, in which case the program goes to Step 6.
*/

Munkres::Munkres(int nRow, int nCol): nRow_(nRow), nCol_(nCol), pathRow0(0), pathCol0(0), pathCount(0)
{ 
	costs_.resize(nRow_);
	for (int r = 0; r < nRow_; ++r) {
		costs_[r].resize(nCol_, 0);
	}

	marks_.resize(nRow_);
	for (int r = 0; r < nRow_; ++r) {
		marks_[r].resize(nCol_);   // 1 is star zero, 2 is prime zero 
	}

	rowCover_.resize(nRow_);     // record the row whether be convered 
	colCover_.resize(nCol_);     // record the col whether be convered 
	 
	augPaths_.resize(nRow_ * 2);    // max save 2 in one row
	for (int r = 0; r < nRow_ * 2; ++r) {
		augPaths_[r].resize(2);   // record the augment path
	} 
}

Munkres::~Munkres()
{ 
	costs_.clear();
	augPaths_.clear();
	marks_.clear();
	rowCover_.clear();
	colCover_.clear();
}

bool Munkres::Build(const std::vector<std::vector<int> >& costs)
{ 
	costs_ = costs;
	return true;
}
 

float Munkres::Compute(const std::vector<std::vector<int> >& costs, std::vector<int>& pairs)
{  
	costs_ = costs;
	bool done = false;
	int step = 1;
	while (!done) {
		ShowCostMatrix();
		ShowMaskMatrix();
		std::cout << "step: " << step << std::endl;
		switch (step) {
		case 1:
			StepOne(step);
			break;
		case 2:
			StepTwo(step);
			break;
		case 3:
			StepThree(step);
			break;
		case 4:
			StepFour(step);
			break;
		case 5:
			StepFive(step);
			break;
		case 6:
			StepSix(step);
			break;
		case 7:
			StepSeven(pairs);
			done = true;
            std::cout << "=========Done!===========" << std::endl;
			break;
		}  
	}

	float minCost = 0.0;
	for (int r = 0; r < pairs.size()-1; r += 2) {
		minCost += costs[pairs[r]][pairs[r + 1]];
	}
	return minCost;
}


void Munkres::StepOne(int& step)
{
	/*
	For each row of the matrix, find the smallest element and subtract it from every element in its row. 
	Go to Step 2. We can define a local variable called minval that is used to hold the smallest value in a row.  
	Notice that there are two loops over the index j appearing inside an outer loop over the index i. 
	The first inner loop over the index j searches a row for the minval.  
	Once minval has been found this value is subtracted from each element of that row in the second inner loop over j.  
	The value of step is set to 2 just before stepone ends.
	*/
	int minInRow;
	for (int r = 0; r < nRow_; ++r) {
		minInRow = costs_[r][0];
		for (int c = 1; c < nCol_; ++c) {
			if (costs_[r][c] < minInRow) {
				minInRow = costs_[r][c];
			}
		}
		for (int c = 0; c < nCol_; ++c) {
			costs_[r][c] -= minInRow;
		}
	}
	step = 2;
}

void Munkres::StepTwo(int& step)
{
	/*
	Find a zero (Z) in the resulting matrix.  If there is no starred zero in its row or column, star Z. 
	Repeat for each element in the matrix. Go to Step 3.  In this step, we introduce the mask matrix M,
	which in the same dimensions as the cost matrix and is used to star and prime zeros of the cost matrix.  
	If M(i,j)=1 then C(i,j) is a starred zero,  If M(i,j)=2 then C(i,j) is a primed zero.  
	We also define two vectors R_cov and C_cov that are used to “cover” the rows and columns of the cost matrix C. 
	In the nested loop (over indices i and j) we check to see if C(i,j) is a zero value and if its column or row is not already covered.  
	If not then we star this zero (i.e. set M(i,j)=1) and cover its row and column (i.e. set R_cov(i)=1 and C_cov(j)=1).  
	Before we go on to Step 3, we uncover all rows and columns so that we can use the cover vectors to help us count the number of starred zeros.
	*/
	for (int r = 0; r < nRow_; ++r) {
		for (int c = 0; c < nCol_; ++c) {
			if (costs_[r][c] == 0 && rowCover_[r] == 0 && colCover_[c] == 0) {
				marks_[r][c] = 1;
				rowCover_[r] = 1;
				colCover_[c] = 1;
			}
		}
	}
	ClearCovers();
	step = 3;
}

void Munkres::StepThree(int& step)
{
	/*
	Cover each column containing a starred zero.  If K columns are covered, the starred zeros describe a complete set of unique assignments.  
	In this case, Go to DONE, otherwise, Go to Step 4. Once we have searched the entire cost matrix, we count the number of independent zeros found.  
	If we have found (and starred) K independent zeros then we are done.  If not we procede to Step 4.
	*/ 
	for (int r = 0; r < nRow_; ++r) {
		for (int c = 0; c < nCol_; ++c) {
			if (marks_[r][c] == 1) {
				colCover_[c] = 1;
			}
		}
	}
	int colCount = 0;
	for (int c = 0; c < nCol_; ++c) {
		if (colCover_[c] == 1) {
			colCount += 1;
		}
	}
	
	if (colCount >= nCol_ || colCount >= nRow_) {
		step = 7;
	}
	else {
		step = 4;
	}
}

void Munkres::StepFour(int& step)
{
	/*
	Find a noncovered zero and prime it.  If there is no starred zero in the row containing this primed zero, Go to Step 5.  
	Otherwise, cover this row and uncover the column containing the starred zero. Continue in this manner until there are no uncovered zeros left. 
	Save the smallest uncovered value and Go to Step 6.
	*/
	int row = -1;
	int col = -1;
	bool done = false;

	while (!done) {
		FindAZero(row, col);
		if (row == -1) {
			done = true;
			step = 6;
		}
		else {
			marks_[row][col] = 2;
			if (StarInRow(row)) {
				FindStarInRow(row, col);
				rowCover_[row] = 1;
				colCover_[col] = 0;
			}
			else {
				done = true;
				step = 5;
				pathRow0 = row;
				pathCol0 = col;
			}
		}
	}
}

void Munkres::StepFive(int& step)
{
	/*
	Construct a series of alternating primed and starred zeros as follows.  Let Z0 represent the uncovered primed zero found in Step 4.  
	Let Z1 denote the starred zero in the column of Z0 (if any). Let Z2 denote the primed zero in the row of Z1 (there will always be one). 
	Continue until the series terminates at a primed zero that has no starred zero in its column.  
	Unstar each starred zero of the series, star each primed zero of the series, erase all primes and uncover every line in the matrix.  Return to Step 3.  
	You may notice that Step 5 seems vaguely familiar.  
	It is a verbal description of the augmenting path algorithm (for solving the maximal matching problem) which we discussed in Lecture 3.  
	We decompose the operations of this step into a main procedure and five relatively simple subprograms.
	*/
	
	int r = -1;
	int c = -1;

	pathCount = 1;
	augPaths_[pathCount - 1][0] = pathRow0;
	augPaths_[pathCount - 1][1] = pathCol0;
	bool done = false;
	while (!done) {
		FindStarInCol(augPaths_[pathCount - 1][1], r);
		if (r > -1) {
			pathCount += 1;
			augPaths_[pathCount - 1][0] = r;
			augPaths_[pathCount - 1][1] = augPaths_[pathCount - 2][1];
		}
		else {
			done = true;
		}
		if (!done) {
			FindPrimeInRow(augPaths_[pathCount - 1][0], c);
			pathCount += 1;
			augPaths_[pathCount - 1][0] = augPaths_[pathCount - 2][0];
			augPaths_[pathCount - 1][1] = c;
		}
	}
	AugmentPath();
	ClearCovers();
	ErasePrimes();
	step = 3;
}

void Munkres::StepSix(int& step)
{
	/*
	Add the value found in Step 4 to every element of each covered row, and subtract it from every element of each uncovered column. 
	Return to Step 4 without altering any stars, primes, or covered lines. Notice that this step uses the smallest uncovered value in the cost matrix to modify the matrix.  
	Even though this step refers to the value being found in Step 4 it is more convenient to wait until you reach Step 6 before searching for this value.  
	It may seem that since the values in the cost matrix are being altered, we would lose sight of the original problem.  
	However, we are only changing certain values that have already been tested and found not to be elements of the minimal assignment.  
	Also we are only changing the values by an amount equal to the smallest value in the cost matrix, 
	so we will not jump over the optimal (i.e. minimal assignment) with this change.
	*/

	float minVal = std::numeric_limits<float>::max();
	FindSmallest(minVal);
	for (int r = 0; r < nRow_; ++r) {
		for (int c = 0; c < nCol_; ++c) {
			if (rowCover_[r] == 1) {
				costs_[r][c] += minVal;
			}
			if (colCover_[c] == 0) {
				costs_[r][c] -= minVal;
			}
		}
	}
	step = 4;
}

void Munkres::StepSeven(std::vector<int>& pairs)
{
	for (int r = 0; r < nRow_; ++r) {
		pairs.emplace_back(r);
		int c = -1;
		FindStarInRow(r, c);
		pairs.emplace_back(c);
	}
}


void Munkres::FindAZero(int& row, int& col)
{
	int r = 0;
	int c;
	bool done = false;
	row = -1;
	col = -1;
	while (!done) {
		c = 0;
		while (true) {
			if (costs_[r][c] == 0 && rowCover_[r] == 0 && colCover_[c] == 0) {
				row = r;
				col = c;
				done = true;
			}
			c += 1;
			if (c >= nCol_ || done) {
				break;
			}
		}
		r += 1;
		if (r >= nRow_) {
			done = true;
		}
	}
}

bool Munkres::StarInRow(int row)
{
	bool tmp = false;
	for (int c = 0; c < nCol_; ++c) {
		if (marks_[row][c] == 1) {
			tmp = true;
			break;
		}
	}

	return tmp;
}

void Munkres::FindStarInRow(int row, int& col)
{
	col = -1;
	for (int c = 0; c < nCol_; ++c) {
		if (marks_[row][c] == 1) {
			col = c;
			break;
		}
	}
}
 
void Munkres::FindStarInCol(int col, int& row)
{
	row = -1;
	for (int r = 0; r < nRow_; ++r) {
		if (marks_[r][col] == 1) {
			row = r;
			break;
		}
	}
}

void Munkres::FindPrimeInRow(int row, int& col)
{
	for (int c = 0; c < nCol_; ++c) {
		if (marks_[row][c] == 2) {
			col = c;
			break;
		}
	}
}

void Munkres::AugmentPath()
{
	for (int p = 0; p < pathCount; ++p) {
		if (marks_[augPaths_[p][0]][augPaths_[p][1]] == 1) {
			marks_[augPaths_[p][0]][augPaths_[p][1]] = 0;
		}
		else {
			marks_[augPaths_[p][0]][augPaths_[p][1]] = 1;
		}
	}
}

void Munkres::ClearCovers()
{
	for (int r = 0; r < nRow_; ++r) {
		rowCover_[r] = 0;
	}
	for (int c = 0; c < nCol_; ++c) {
		colCover_[c] = 0;
	}
}
void Munkres::ErasePrimes()
{
	for (int r = 0; r < nRow_; ++r) {
		for (int c = 0; c < nCol_; ++c) {
			if (marks_[r][c] == 2) {
				marks_[r][c] = 0;
			}
		}
	}
}

void Munkres::FindSmallest(float& minVal)
{
	for (int r = 0; r < nRow_; ++r) {
		for (int c = 0; c < nCol_; ++c) {
			if (rowCover_[r] == 0 && colCover_[c] == 0) {
				if (minVal > costs_[r][c]) {
					minVal = costs_[r][c];
				}
			}
		}
	}
}

void Munkres::ShowCostMatrix()
{
	std::cout << "-----Costs-----" << std::endl;
	for (int r = 0; r < nRow_; ++r) {
		for (int c = 0; c < nCol_; ++c) {
			std::cout << costs_[r][c] << " ";
		}
		std::cout << std::endl;
	}
}

void Munkres::ShowMaskMatrix()
{
	std::cout << "*****Marks*****" << std::endl;
	for (int r = 0; r < nRow_; ++r) {
		for (int c = 0; c < nCol_; ++c) {
			std::cout << int(marks_[r][c]) << " ";
		}
		std::cout << std::endl;
	}
	std::cout << "===========================" << std::endl;
}

int main()
{
	int nRow = 3;
	int nCol = 3;
	std::vector<std::vector<int> > costs { {1, 2, 3}, {2, 4, 6}, {3, 6, 9} };

	Munkres munkres = Munkres(nRow, nCol);
	 
	//munkres.Build(costs);
	std::vector<int> pairs;
	float cost = munkres.Compute(costs, pairs);
	std::cout << "cost: " << cost << std::endl;
	
	for (int i = 0; i < pairs.size() - 1; i += 2) {
		std::cout << pairs[i] << "==>" << pairs[i + 1] << std::endl;
	}
	return 0;
}


标签:Step,Munkres,void,zero,---,int,算法,row
From: https://www.cnblogs.com/xiaxuexiaoab/p/17838305.html

相关文章

  • (15-418)Lecture 2 A Modern Multi-Core Processor
    并行执行的方式本节内容围绕一个程序,针对如何改变处理器的结构来加速程序提出了多个idea示例程序:该程序通过泰勒展开式来估算sin(x)的值一个最简单的处理器,每个时钟周期只能执行一条指令,而利用ILP(指令级平行)的超标量处理器可同时执行两条指令(当指令间不存在依赖时),但示例程序......
  • 808-ORI-D3R600服务器-多路PCIe3.0的双CPU通用工作站
       一、机箱功能和技术指标:系统系统型号ORI-SR630主板支持EEB(12'*13')/CEB(12'*10.5')/ATX(12'*9.6')/MicroATX前置硬盘最大支持8个3.5寸(兼容25寸)SATA硬盘+2*2.5(后置)电源类型CRPS元余电源,标准ATX电源散热系......
  • #2023-2024-1 20231308 《计算机基础与程序设计》第九周学习总结
    2023-2024-120231308《计算机基础与程序设计》第九周学习总结作业信息这个作业属于哪个课程2023-2024-1-计算机基础与程序设计这个作业要求在哪里2023-2024-1计算机基础与程序设计第九周作业这个作业的目标计算机科学概论第10,11章《C语言程序设计》第8章并完成......
  • Docker部署code-server
    一、简介code-server是一款运行在浏览器界面上的可以安装在任何机器上的VSCode程序,code-server不仅继承了VSCode的使用逻辑,丰富的插件,在VSCode的基础上还提供了更多VSCode所没有的特性,满足更多的使用场景和业务需求。二、安装code-server绿联DX4600为例,首先我们打开Docker......
  • m基于Faster-RCNN网络的猫脸检测和猫眼定位系统matlab仿真,带GUI界面
    1.算法仿真效果matlab2022a仿真结果如下:  2.算法涉及理论知识概要       猫作为一种受欢迎的宠物,其图像在互联网上大量存在。对猫脸和猫眼进行准确检测和定位,在宠物识别、情感分析等领域具有广泛的应用价值。然而,由于猫脸和猫眼的多样性以及复杂背景的干扰,传统......
  • 线程池-常见问题
    尽管线程池对于构建多线程应用是个很强大的机制,但它也不是没有缺点的。使用线程池构建的应用会面临其他多线程应用所面对的一样的并发风险,比如同步错误和死锁,此外线程池还有其他的一些特有缺陷,比如线程池-关联死锁,资源不足,还有线程泄漏。 死锁任何多线程应用都会面临死锁的......
  • 2基础--商品服务
    基础--商品服务商品服务一、品牌管理1、JSR303数据校验(B站p66集)(注意流程)1)使用javax包下自带的验证注解在需要校验的接口上加上@Valid注解给Bean添加校验注解:javax.validation.constraints包下的,并定义自己的message提示在需要检验的参数vo中的字段上添加相应校......
  • 3高级篇--商城业务--压测--缓存--分布式锁
    高级篇--商城业务部分本笔记从谷粒商城的P141(性能测试)开始记录一、性能与压力测试​ 压力测试考察当前软硬件环境下系统所能承受的最大负荷并帮助找出系统瓶颈所在。压测都是为了系统在线上的处理能力和稳定性维持在一个标准范围内,做到心中有数。​ 使用压力测试,我们有希望......
  • 相交 - 点是否在射线上
    效果 //点是否在射线上publicstaticboolIsPointOnRay(Vector2o,Vector2dir,Vector2p){varop=p-o;if(Mathf.Approximately(op.sqrMagnitude,0))//o和p重叠returntrue;//共线且夹角为0度则在射线上floatcross=V2Cross(ref......
  • Java算法练习—递归/回溯
    递归是一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。因此递归过程,最重要的就是查看能不能将原本的问题分解为更小的子问题,这是使用递归的关键。如果是线型递归,子问题直接回到父问题......