匈牙利算法实现蚂蚁找洞
题目描述:
在一个网络上有若干只蚂蚁和洞穴,在每个单位时间内每只蚂蚁可以往水平方向或垂直方向移动一步,走到相邻的
洞穴中。对于每只蚂蚁,走一步需要损耗一单位能量,直到他走入洞穴,且每个洞穴只能容纳一只蚂蚁。求让这些
蚂蚁移动到这些不同的洞穴所需要损耗的最小能量。
- 数据输入:
M*N (1<M,N<100)
网络地图,地图中的H
和a
分别表示洞穴和蚂蚁的位置,个数相同,最多有100
个洞穴,其它空位置用.
表示。 - 结果输出:损耗的最小能量。
输入样例:
H | H | . | . | a |
---|---|---|---|---|
. | . | . | . | . |
. | . | . | . | . |
. | . | . | . | . |
a | a | . | . | H |
输出样例
10
解决方案
网上的匈牙利算法,大多数是关于最大匹配和最小匹配,并没有多少关于完美匹配的具体解释,于是在搜索一个小时无果之后,想到了B站,在上面看到了一个关于匈牙利算法的具体描述,讲的也很好(链接在后面)
。于是在学习之后,我就完全按照老师的思路把代码写出来了,当然也有很多可以优化的地方。
#include <iostream>
#include <vector>
#include <math.h>
using namespace std;
// 两点之间的距离
int distant(pair<int, int> a, pair<int, int> b)
{
return abs(a.first - b.first) + abs(a.second - b.second);
}
// 找出0元素最少的行或者列
int find_min(vector<int> array)
{
int ret;
int min0 = 200;
for (int i = 0; i < array.size(); i++)
{
if (array[i] != 0 && min0 > array[i])
{
min0 = array[i];
ret = i;
}
}
return ret;
}
// 每行/列0元素的个数
vector<int> sum_zero(vector<vector<int>> Map, bool row)
{
vector<int> ret(Map.size(), 0);
for (int i = 0; i < Map.size(); i++)
{
for (int j = 0; j < Map.size(); j++)
{
if (Map[i][j] == 0)
{
if (row)
{
ret[i]++;
}
else
{
ret[j]++;
}
}
}
}
return ret;
}
void step1(vector<vector<int>> &Map)
{
int s = Map.size();
vector<int> minrow(s, 999);
vector<int> mincol(s, 999);
for (int i = 0; i < s; i++)
{
for (int j = 0; j < s; j++)
{
minrow[i] = min(minrow[i], Map[i][j]);
}
}
for (int i = 0; i < s; i++)
{
for (int j = 0; j < s; j++)
{
Map[i][j] -= minrow[i];
}
}
for (int i = 0; i < s; i++)
{
for (int j = 0; j < s; j++)
{
mincol[i] = min(mincol[i], Map[j][i]);
}
}
for (int i = 0; i < s; i++)
{
if (mincol[i] != 0)
{
for (int j = 0; j < s; j++)
{
Map[j][i] -= mincol[i];
}
}
}
}
void step2(vector<vector<int>> &Map)
{
int s = Map.size();
vector<int> zerorow(s, 0);
vector<int> zerocol(s, 0);
// 0元素的总数
int sumzero = 0;
// 记录每一行的0元素的个数
zerorow = sum_zero(Map, true);
for (int i = 0; i < s; i++)
sumzero += zerorow[i];
// 找出只有一个0元素的行,给这个0画圈,同时划去该列其他0元素
for (int i = 0; i < s; i++)
{
if (zerorow[i] == 1)
{
// lcation表示只有一个0所在的列
int location;
for (int j = 0; j < s; j++)
{
if (Map[i][j] == 0)
{
// 1000表示划圈
Map[i][j] = 1000;
location = j;
sumzero--;
break;
}
}
for (int j = 0; j < s; j++)
{
if (Map[j][location] == 0)
{
// 2000表示划线
Map[j][location] = 2000;
sumzero--;
}
}
}
}
// 记录每一列的0元素的个数
zerocol = sum_zero(Map, false);
// 找出只有一个0元素的列,给这个0元素画圈,同时划去该行其他0元素
for (int i = 0; i < s; i++)
{
if (zerocol[i] == 1)
{
// location表示0元素所在的行
int location;
for (int j = 0; j < s; j++)
{
if (Map[j][i] == 0)
{
// 1000表示画圈
Map[j][i] = 1000;
location = j;
sumzero--;
break;
}
}
for (int j = 0; j < s; j++)
{
if (Map[location][j] == 0)
{
Map[location][j] = 2000;
sumzero--;
}
}
}
}
// 如果存在还没有标记的0
while (sumzero != 0)
{
// 每行0元素的个数
zerorow = sum_zero(Map, true);
// 0元素最少的行
int row_location = find_min(zerorow);
for (int i = 0; i < s; i++)
{
zerocol[i] = 0;
if (Map[row_location][i] == 0)
{
for (int j = 0; j < s; j++)
{
if (Map[j][i] == 0)
{
zerocol[i]++;
}
}
}
}
// 0元素最少行中0元素对应列中最少的列
int col_location = find_min(zerocol);
// 那个0画圈
Map[row_location][col_location] = 1000;
sumzero--;
// 这行这列的其他0划去
for (int i = 0; i < s; i++)
{
if (Map[row_location][i] == 0)
{
// 划去
Map[row_location][i] = 2000;
sumzero--;
}
}
for (int i = 0; i < s; i++)
{
if (Map[i][col_location] == 0)
{
// 划去
Map[i][col_location] = 2000;
sumzero--;
}
}
}
}
void step3(vector<vector<int>> &Map, vector<bool> &row, vector<bool> &col)
{
int s = Map.size();
// 在没有画圈的行画勾
for (int i = 0; i < s; i++)
{
int mark = 0;
for (int j = 0; j < s; j++)
{
if (Map[i][j] == 1000)
{
mark = 1;
break;
}
}
if (mark == 0)
row[i] = true;
}
// 在画勾的行的花掉0元素的列画勾
for (int i = 0; i < s; i++)
{
if (row[i])
{
for (int j = 0; j < s; j++)
{
if (Map[i][j] == 2000)
{
col[j] = true;
}
}
}
}
// 在画勾的列中含有圈0元素的行画勾
for (int i = 0; i < s; i++)
{
if (col[i])
{
for (int j = 0; j < s; j++)
{
if (Map[j][i] == 1000)
{
row[j] = true;
}
}
}
}
}
void step4(vector<vector<int>> &Map, vector<bool> row, vector<bool> col)
{
int s = Map.size();
// 在画勾的行和没有画勾的列中找到最小的元素
int minnumber = 666;
for (int i = 0; i < s; i++)
{
if (row[i])
{
for (int j = 0; j < s; j++)
{
if (!col[j])
{
minnumber = min(Map[i][j], minnumber);
}
}
}
}
// 查找画勾的列
vector<int> tmp_col;
for (int i = 0; i < s; i++)
{
if (col[i])
tmp_col.push_back(i);
}
// 对画勾的行减去最小元素
// 对画勾的列增加最小元素
for (int i = 0; i < s; i++)
{
if (row[i])
{
for (int j = 0; j < s; j++)
{
if (!col[j])
{
Map[i][j] -= minnumber;
}
}
}
else
{
for (int j = 0; j < tmp_col.size(); j++)
{
Map[i][tmp_col[j]] += minnumber;
}
}
}
}
// 检查是否符合
int check(vector<vector<int>> &Map)
{
int s = Map.size();
for (int i = 0; i < s; i++)
{
int markrow = 0;
int markcol = 0;
for (int j = 0; j < s; j++)
{
if (Map[i][j] == 1000 || Map[i][j] == 0)
{
markrow++;
}
if (Map[j][i] == 1000 || Map[j][i] == 0)
{
markcol++;
}
}
if (!(markcol == 1 && markrow == 1))
return -1;
}
return 0;
}
// 把1000和2000归零
void to_zero(vector<vector<int>> &Map)
{
int s = Map.size();
for (int i = 0; i < s; i++)
{
for (int j = 0; j < s; j++)
{
if (Map[i][j] == 1000 || Map[i][j] == 2000)
Map[i][j] = 0;
}
}
}
int main()
{
int M, N;
// 输入地图的行和列
cin >> M >> N;
int ant = 0;
vector<vector<char>> Map(M, vector<char>(N));
// 蚂蚁的位置和洞穴的位置
vector<pair<int, int>> antlocation;
vector<pair<int, int>> holelocation;
// 输入地图
for (int i = 0; i < M; i++)
{
for (int j = 0; j < N; j++)
{
cin >> Map[i][j];
if (Map[i][j] == 'a')
{
ant++;
antlocation.push_back({i, j});
}
else if (Map[i][j] == 'H')
{
holelocation.push_back({i, j});
}
}
}
vector<vector<int>> hole(ant, vector<int>(ant));
// 计算每只蚂蚁和每个洞穴的距离
for (int i = 0; i < ant; i++)
{
for (int j = 0; j < ant; j++)
{
hole[i][j] = distant(antlocation[i], holelocation[j]);
}
}
vector<vector<int>> HOLE = hole;
// 开始进行匈牙利算法
step1(hole);
if (check(hole) == -1)
{
while (1)
{
step2(hole);
if (check(hole) == 0)
break;
vector<bool> row(ant, false);
vector<bool> col(ant, false);
step3(hole, row, col);
step4(hole, row, col);
cout << endl;
if (check(hole) == 0)
break;
to_zero(hole);
}
}
// 计算结果
int result = 0;
for (int i = 0; i < ant; i++)
{
for (int j = 0; j < ant; j++)
{
if (hole[i][j] == 0 || hole[i][j] == 1000)
result += HOLE[i][j];
}
}
// 输出结果
cout << "The answer is:" << result << endl;
return 0;
}
/*测试样例
5 5
H H . . a
. . . . .
. . . . .
. . . . .
a a . . H
7 8
. . . H . . . .
. . . H . . . .
. . . H . . . .
a a a H a a a a
. . . H . . . .
. . . H . . . .
. . . H . . . .
*/
参考资料: https://www.bilibili.com/video/BV1hF411h7eX