匈牙利算法实现蚂蚁找洞

匈牙利算法实现蚂蚁找洞

题目描述:
在一个网络上有若干只蚂蚁和洞穴,在每个单位时间内每只蚂蚁可以往水平方向或垂直方向移动一步,走到相邻的
洞穴中。对于每只蚂蚁,走一步需要损耗一单位能量,直到他走入洞穴,且每个洞穴只能容纳一只蚂蚁。求让这些
蚂蚁移动到这些不同的洞穴所需要损耗的最小能量。

  • 数据输入M*N (1<M,N<100)网络地图,地图中的Ha分别表示洞穴和蚂蚁的位置,个数相同,最多有100
    个洞穴,其它空位置用.表示。
  • 结果输出:损耗的最小能量。

输入样例:

HH..a
.....
.....
.....
aa..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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值