基于回溯算法的排班问题求解

基于回溯算法的排班问题求解

    • @[TOC](基于回溯算法的排班问题求解)
  • 前言
  • 一、排班问题抽象
  • 二、回溯算法具体实现
    • 1.函数主体
    • 2.列元素求和
    • 3.回溯算法
    • 4.回溯算法出口函数
  • 总结

前言

排班问题应用非常广泛,之前给咖驿站做【自动化排班表】的时候遇到过。当时在Matlab里设计相应的评价函数,采用【粒子群算法】把这个问题解决了。在b站也有up主采用【遗传算法】去实现了排班问题的求解,链接如下:

https://www.bilibili.com/video/BV1xb4y1Y7d3?spm_id_from=333.337.search-card.all.click

这一次采用回溯算法去解决这个问题,编程语言为C++。


一、排班问题抽象

排班问题可以抽象为如何调节两大主体的供需关系。

以值班为例,主体分别为【值班人员】与【值班时间】。比如说有2位值班人员,2个值班时间,【值班人员A1】的需求是【值班时间B1】,【值班人员A2】的需求是【值班时间B2】,那么可以抽象为如下的矩阵:

1 0
0 1

那么排班问题即为如何保证【每个人值班次数不少于一定限制】,【每个值班时间不超过一定限制】。那么具体到矩阵而言,即为

【如何将一定数量的1置为0,从而保证每行不超过规定限制,每列不超过规定限制。】

二、回溯算法具体实现

1.函数主体

/*
* paiban 排班问题主函数
* mat 待排班的矩阵 每行为值班人员 每列为值班时间
* rqm 行要求,最终结果应每行小于等于rqm
* rqn 列要求,最终结果应每列大于等于rqn 
* 返回值 1 该问题有解 0 该问题无解
*/
bool paiban(vector<vector<int>>& mat, int rqm, int rqn) {
    int m = mat.size(), n = mat[0].size();
    bool ans = false, flag = false;
    // 如果第i个值班时间最多只有小于rqn个值班人员,那么这一算法无解
    for (int i = 0; i < n; ++i) {
    		 // col_count列元素求和
        if (col_count(mat, i) < rqn) return false;
    }
    // 否则可能有解
    for (int j = 0; j < n; ++j) { // j为列
        if (flag) break;
        for (int i = 0; i < m; ++i) { // i为行
            if (mat[i][j] == 1 && col_count(mat, j) > rqn) {
                mat[i][j] = 0;
                ans = ans || backtracking(mat, m, n, i, j, rqm, rqn);
                if (ans) {
                    flag = true;
                    break;
                }
                mat[i][j] = 1;
            } 
        }
    }
    return ans;
}

2.列元素求和

对于vector >类型,没有对应的列求和函数,需要自行撰写:

/*
* col_count 列求和函数
* mat 待排班的矩阵 每行为值班人员 每列为值班时间
* c 列数
* 返回值 该列元素之和
*/
int col_count(vector<vector<int>> mat, int c) {
    int m = mat.size(), ans = 0;
    for (int i = 0; i < m; ++i) {
        ans += mat[i][c];
    }
    return ans;
}

3.回溯算法

回溯算法搜索解空间:

/*
* backtracking 回溯函数
* mat 待排班的矩阵 每行为值班人员 每列为值班时间
* m 行数
* n 列数
* r 搜索起始点所在行
* c 搜索起始点所在列
* rqm 行要求,最终结果应每行小于等于rqm
* rqn 列要求,最终结果应每列大于等于rqn 
* 返回值 1 搜索到解 0 未搜索到解
*/
bool backtracking(vector<vector<int>>& mat, int m, int n, int r, int c, int rqm, int rqn) {
    // 先确定出口条件
    if (meetrequire(mat, m, n, rqm, rqn)) return true;
    bool ans = false, flag = false;
    // 遍历完第c列
    if (r < m - 1) {
        for (int i = r + 1; i < m; ++i) {
            if (mat[i][c] == 1 && col_count(mat, c) > 2) {
                mat[i][c] = 0;
                ans = ans || backtracking(mat, m, n, i, c, rqm, rqn);
                if (ans) {
                    flag = true;
                    break;
                }
                mat[i][c] = 1;
            } 
        }
    }
    // 再遍历c+1列及之后
    if (c < n - 1) {
        for (int j = c + 1; j < n; ++j) { // j为列
            if (flag) break;
            for (int i = 0; i < m; ++i) { // i为行
                if (mat[i][j] == 1 && col_count(mat, j) > 2) {
                    mat[i][j] = 0;
                    ans = ans || backtracking(mat, m, n, i, j, rqm, rqn);
                    if (ans) {
                        flag = true;
                        break;
                    }
                    mat[i][j] = 1;
                } 
            }
        }
    }
    return ans;
}

4.回溯算法出口函数

回溯算法的出口是搜索到满足条件的mat并返回true:

/*
* meetrequire 判断是否搜索到符合条件的矩阵
* mat 待排班的矩阵 每行为值班人员 每列为值班时间
* m 行数
* n 列数
* rqm 行要求,最终结果应每行小于等于rqm
* rqn 列要求,最终结果应每列大于等于rqn 
* 返回值 1 符合条件 0 不符合条件
*/
bool meetrequire(vector<vector<int>>& mat, int m, int n, int rqm, int rqn) {
    // 检查每一行
    for (int i = 0; i < m; ++i) {
        if (count(mat[i].begin(), mat[i].end(), 1) > rqm) return false;
    }
    // 检查每一列
    for (int i = 0; i < n; ++i) {
        if (col_count(mat, i) < rqn) return false;
    }
    return true;
}

总结

以上是通过回溯算法求解排班问题的思路及C++代码。

你可能感兴趣的:(c++,算法)