基于Cplex的人员排班问题建模求解(JavaAPI)

news2024/11/29 10:58:16

使用Java调用Cplex实现了阿里mindopt求解器的案例(https://opt.aliyun.com/platform/case)人员排班问题。

这里写目录标题

  • 人员排班问题
  • 问题描述
  • 数学建模
  • 编程求解(Cplex+JavaAPI)
  • 求解结果

人员排班问题

随着现在产业的发展,7*24小时服务的需要,人员排班的问题,逐渐成为了企业管理中的重要环节。人员排班在许多行业都具有广泛的应用价值,主要包括以下几个方面:

  • 制造业:生产车间的人员分配、班次安排和轮班计划等,需要根据产线的工作要求和员工的技能特点进行合理的排班。
  • 医疗行业:医院、诊所等机构需要对医生、护士等员工进行排班。
  • 餐饮业:餐厅、咖啡馆等服务场所需要根据客流高峰期和低谷期合理安排员工的工作时间。
  • 零售业:商场、超市等零售场所需要根据营业时间、客流量和节假日等因素进行人员排班。
  • 旅游业:景区、酒店等旅游设施需要根据旅游旺季、淡季和客流量变化对员工进行排班。
  • 客服中心:呼叫中心、在线客服等服务机构需要根据客户咨询需求进行员工排班。

总之,人员排班在各行各业都具有重要的实际应用价值,可以帮助企业和机构提高管理效率、降低成本,同时提升员工的工作满意度和整体效能。总之,人员排班在各行各业都具有重要的实际应用价值,可以帮助企业和机构提高管理效率、降低成本,同时提升员工的工作满意度和整体效能。

运筹学中的数学规划方法是计算人员排班问题的一个好方案。人员排班问题在建模时需要考虑多种约束条件,比如:

  • 用工需求约束:根据各岗位的工作任务和生产要求,保证每个岗位在每个时间段内有足够的员工进行工作。
  • 员工能力约束:不同岗位可能需要不同的技能和经验,需要确保安排到相应岗位的员工具备相关的能力和资质。
  • 工作时间约束:员工的工作时间需要遵守相关法律法规,比如每天工作时间上限、休息时间要求等。此外,还需要考虑员工的工作时间偏好,如部分员工可能只能接受特定时间段的工作安排。
  • 连续工作天数约束:为保证员工的工作质量和身体健康,通常要求连续工作天数不超过一定限制。以及员工在一定时间周期内有休假要求,需要确保他们的休假安排得到满足。
  • 公平性约束:为保障员工的权益,要求在满足以上约束的前提下,尽量平衡各员工的工作时间和任务分配,避免出现工作负担不均衡的情况。
  • 员工偏好:如每个员工有自己更喜欢的上班的时间、岗位、或者协作同事配合等。

我们需要考虑企业内各岗位的需求、员工的工作能力以及工作时间的限制等因素。此外,还需关注企业成本与员工满意度的权衡,以确保在合理控制成本的前提下,最大程度地提高员工的工作满意度。属于一个约束复杂,且多目标的问题。在用数学规划方法进行排班时,建议做一些业务逻辑简化问题,否则容易出现问题太大或者不可解的情况。

下面我们将通过一个简单的例子,讲解如何使用数学规划的方法来做人员排班。

问题描述

个公司有客服岗工作需要安排,不同时间段有不同的用户需求。该公司安排员工上班的班次有三种:早班8-16点、晚班16-24点和夜班0-8点。一周员工最多安排5天上班,最少休息2天。需要保障值班员工能满足需求,且要保障员工休息时间,如前一天安排晚班后,第二天不能安排早班。

请问怎么安排总上班的班次最少,此时的班表是什么样的?

数学建模

在这里插入图片描述
在这里插入图片描述

编程求解(Cplex+JavaAPI)

复制代码不能直接运行,需要在IDEA pom.xml中导入阿帕奇读取csv文件的依赖,并且需要导入cplex.jar。
数据可在文章开头阿里mindopt案例地址中获取。

<dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-csv</artifactId>
            <version>1.7</version>
        </dependency>
package main.java;

import ilog.concert.*;
import ilog.cplex.IloCplex;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVRecord;

import java.util.logging.Logger;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.stream.IntStream;

public class EmpSchedulingProblem {


    public int n_employees;
    public int n_days;
    public int n_shifts;
    int[] days;
    int[] shifts;
    int[] employees;
    int[][] demandOfEmployees;
    public static Logger logger = Logger.getLogger("myLogger");

    /**
     * @param day   某天
     * @param shift 某个班次
     * @return 某天某班次需求的人数
     */
    public int getDemandOfEmployees(int day, int shift) {
        return demandOfEmployees[day][shift];
    }

    public EmpSchedulingProblem() throws IOException {
        demandOfEmployees = this.readFile();
        employees = IntStream.range(0, n_employees).toArray();
        days = IntStream.range(0, n_days).toArray();
        shifts = IntStream.range(0, n_shifts).toArray();
    }

    public int[][] readFile() throws IOException {
        this.n_shifts = 0;
        try (Reader reader = Files.newBufferedReader(Paths.get("src/main/java/mindoptdemo/班次.csv"))) {
            Iterable<CSVRecord> records = CSVFormat.DEFAULT.parse(reader);
            records.iterator().next(); // 跳过第一行
            for (CSVRecord record : records) {
                String shift = (record.get(0));   // 星期1到星期7,索引为0,故-1
                n_shifts += 1;
            }


        } catch (IOException e) {
            logger.warning(e.getMessage());
        }
        // 调度周期:7天,3班倒
        this.n_days = (int) Files.lines(Paths.get(new File("src/main/java/mindoptdemo/需求人数.csv").getPath())).count() - 1;
        int[][] day_shift_empNum = new int[n_days][n_shifts];

        // commons-csv读取csv文件,需要导入依赖
        try (Reader reader = Files.newBufferedReader(Paths.get("src/main/java/mindoptdemo/需求人数.csv"))) {
            Iterable<CSVRecord> records = CSVFormat.DEFAULT.parse(reader);
            records.iterator().next(); // 跳过第一行
            for (CSVRecord record : records) {

                int day = Integer.parseInt(record.get(0)) - 1;   // 星期1到星期7,索引为0,故-1
                int morningShiftEmpNum = Integer.parseInt(record.get(1)); // 早班需要员工的数量
                int middleShiftEmpNum = Integer.parseInt(record.get(2));  // 中班需要员工的数量
                int nightShiftEmpNum = Integer.parseInt(record.get(3));   // 晚班需要员工的数量

                //保存至二维数组,某天某班次需要的员工数量
                day_shift_empNum[day][0] = morningShiftEmpNum;
                day_shift_empNum[day][1] = middleShiftEmpNum;
                day_shift_empNum[day][2] = nightShiftEmpNum;

                this.n_employees += morningShiftEmpNum + middleShiftEmpNum + nightShiftEmpNum;
            }
            this.n_employees = (int) Math.ceil((double) (this.n_employees) / 5) + 1;
//            System.out.println("预估排班人数:" + n_employees);
            logger.info("预估排班人数:" + n_employees);
        } catch (IOException e) {
            logger.info(e.getMessage());
        }
        System.out.println(Arrays.deepToString(day_shift_empNum));

        return day_shift_empNum;
    }

    public void cplexSolve() {
        try {
            // 声明cplex优化模型
            IloCplex model = new IloCplex();
            // 声明决策变量,x_ijk表示员工i在第j天上班次k
            IloIntVar[][][] x = new IloIntVar[n_employees][n_days][n_shifts];
            for (int i = 0; i < n_employees; i++) {
                for (int j = 0; j < n_days; j++) {
                    for (int k = 0; k < n_shifts; k++) {
                        // boolVar()声明x_ijk为0-1变量
                        x[i][j][k] = model.boolVar();
                    }
                }
            }

            // 约束:每天各个班次在岗的人数符合需求
            for (int d = 0; d < days.length; d++) {
                for (int s = 0; s < shifts.length; s++) {
                    IloLinearIntExpr expr = model.linearIntExpr();
                    for (int e = 0; e < n_employees; e++) {
                        // addTerm()表示 1*x_eds
                        expr.addTerm(1, x[e][d][s]);
                    }
                    model.addGe(expr, this.getDemandOfEmployees(d, s));
                }
            }
            // 约束:每人每天最多只有一个班次
            for (int n : employees) {
                for (int d : days) {
                    IloLinearIntExpr expr = model.linearIntExpr();
                    for (int s : shifts) {
                        expr.addTerm(1, x[n][d][s]);
                    }
                    model.addLe(expr, 1);
                }
            }

            // 约束:前一天是晚班的,第二天不能是早班
            for (int e : employees) {
                for (int d : days) {
                    IloLinearIntExpr expr = model.linearIntExpr();
                    // 0 早班
                    // 1 中班
                    // 2 晚班
                    // 当天上晚班的员工,第二天不能上早班
                    expr.addTerm(1, x[e][d][2]);
                    if (d == 6) {
                        expr.addTerm(1, x[e][0][0]);
                    } else {
                        expr.addTerm(1, x[e][d + 1][0]);
                    }

                    model.addLe(expr, 1);
                }
            }

            // 约束:一周工作工作时间不能超过5天
            for (int e = 0; e < n_employees; e++) {
                IloLinearIntExpr expr = model.linearIntExpr();
                for (int d = 0; d < days.length; d++) {
                    for (int s = 0; s < shifts.length; s++) {
                        expr.addTerm(1, x[e][d][s]);
                    }
                }
                model.addLe(expr, 5);
            }

            // 目标:雇佣的员工最少,即有排班的班次总数最少
            IloLinearIntExpr expr = model.linearIntExpr();
            for (int e : employees) {
                for (int d : days) {
                    for (int s : shifts) {
                        expr.addTerm(1, x[e][d][s]);
                    }
                }
            }
            model.addMinimize(expr);

            // 打印求解结果
            if (model.solve()) {
                System.out.println("num of employees: " + n_employees);
                System.out.println("solution status: " + model.getStatus());
                System.out.println("solution value: " + model.getObjValue());

                System.out.printf("%-8s", " ");
                for (int d = 0; d < n_days; d++) {
                    System.out.printf("\t%d", d + 1);
                }
                System.out.println();

                for (int e : employees) {
                    System.out.printf("employee%d\t", e + 1);
                    int shiftCount = 0;
                    for (int d : days) {
                        int shift = 0;

                        for (int s : shifts) {
                            if (((int) model.getValue(x[e][d][s])) != 0) {
                                shift = s + 1;
                                shiftCount += 1;
                            }
                        }
                        System.out.printf("%d\t", shift);
                    }
                    System.out.printf("员工%d这周上%d个班次", e + 1, shiftCount);
                    System.out.println();
                }
            }

            model.end();
        } catch (IloException e) {
            logger.warning(e.getMessage());
        }

    }

    public static void main(String[] args) {
        try {
            EmpSchedulingProblem esp = new EmpSchedulingProblem();
            esp.cplexSolve();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

求解结果

每个员工在那一天上第几个班,如图所示,如员工1-周1-不上班,员工2-周2-夜班;0不上班、1早班、2晚班、3夜班。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Leetcode 2119.反转两次的数字

反转 一个整数意味着倒置它的所有位。 例如&#xff0c;反转 2021 得到 1202 。反转 12300 得到 321 &#xff0c;不保留前导零 。 给你一个整数 num &#xff0c;反转 num 得到 reversed1 &#xff0c;接着反转 reversed1 得到 reversed2 。如果 reversed2 等于 num &#x…

Centos7配置firewalld防火墙规则

这里写自定义目录标题 欢迎使用Markdown编辑器一、简单介绍二、特点和功能2.1、区域&#xff08;Zone&#xff09;2.2、运行时和永久配置2.3、服务和端口2.4、动态更新2.5、连接跟踪2.6、D-Bus接口 三、设置规则3.1、启动防火墙服务3.2、新建防火墙规则的服务&#xff0c;添加端…

C/C++进程超详细详解【中部分】(系统性学习day07)

目录 前言 一、守护进程 1.概念 2.守护进程创建的原理&#xff08;如图清晰可见&#xff09; 3.守护进程的实现&#xff08;代码块&#xff09; 二、dup和dup2 1&#xff0c;复制文件描述符 2.文件描述符重定向 三、系统日志 1&#xff0c;打开日志 2&#xff0c;向日…

【MATLAB】字体美化和乱码

文章目录 前言首先说说说字体美化乱码到底是怎么导致的&#xff1f;1 字体导致的乱码2 编码导致的乱码总结 前言 最近打开MATLAB&#xff0c;又发现了一个问题&#xff1a;编辑器中的中文输入在命令行或者说终端输出竟然是乱码&#xff0c;然后赶紧翻阅了一下此前的博客以及未发…

基于Java的校园资料分享平台设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

嵌入式Linux应用开发-基础知识-第十八章系统对中断的处理①

嵌入式Linux应用开发-基础知识-第十八章系统对中断的处理 第十八章 Linux 系统对中断的处理①18.1 进程、线程、中断的核心&#xff1a;栈18.1.1 ARM 处理器程序运行的过程18.1.2 程序被中断时&#xff0c;怎么保存现场18.1.3 进程、线程的概念 18.2 Linux系统对中断处理的演进…

Python实用技术二:数据分析和可视化(2)

目录 一&#xff0c;多维数组库numpy 1&#xff0c;操作函数&#xff1a;​ 2&#xff0c;numpy数组元素增删 1&#xff09;添加数组元素 2&#xff09;numpy删除数组元素 3&#xff09;在numpy数组中查找元素 4&#xff09;numpy数组的数学运算 3&#xff0c;numpy数…

windows系统利用powershell查看系统支持那些Windows功能选项

在PowerShell中&#xff0c;我们可以使用Get-WindowsOptionalFeature cmdlet命令来查看Windows功能选项。 打开PowerShell 输入以下命令&#xff1a;将结果输出到1.log Get-WindowsOptionalFeature -Online >1.log 可以看到在指定路径下看到生成了文件 打开查看内容&…

Linux基础指令(六)

目录 前言1. man 指令2. date 指令3. cal 指令4. bc 指令5. uname 指令结语&#xff1a; 前言 欢迎各位伙伴来到学习 Linux 指令的 第六天&#xff01;&#xff01;&#xff01; 在上一篇文章 Linux基本指令(五) 中&#xff0c;我们通过一段故事线&#xff0c;带大家感性的了…

数学建模Matlab之优化类方法

本文还是来源于http://t.csdnimg.cn/P5zOD&#xff0c;但肯定不可能只有这些内容&#xff0c;否则那位作者应该会打我……&#xff0c;在这里&#xff0c;只是讲解优化类问题比较常用的方法&#xff0c;以TSP问题为例&#xff0c;适合入门。 模拟退火 模拟退火是一种概率算法&a…

软件设计师_数据库系统_学习笔记

文章目录 3.1 数据库模式3.1.1 三级模式 两级映射3.1.2 数据库设计过程 3.2 ER模型3.3 关系代数与元组演算3.4 规范化理论3.5 并发控制3.6 数据库完整性约束3.7 分布式数据库3.8 数据仓库与数据挖掘 3.1 数据库模式 3.1.1 三级模式 两级映射 内模式直接与物理数据库相关联的 定…

PWN Test_your_nc Write UP

目录 PWN 00 解题过程 总结归纳 PWN 01 解题过程 总结归纳 PWN 02 解题过程 总结归纳 PWN 03 解题过程 总结归纳 PWN 04 解题过程 总结归纳 CTF PWN 开始&#xff01; 冲就完了 PWN 00 解题过程 ssh远程链连接 ssh ctfshowpwn.challenge.ctf.show -p28151 输…

Git使用【上】

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;那个传说中的man的主页 &#x1f3e0;个人专栏&#xff1a;题目解析 &#x1f30e;推荐文章&#xff1a;题目大解析3 前言 先前有些git命令我在我的其它文章里面已经写过&#xff0c;若要查看可参考【Linu…

嵌入式Linux应用开发-基础知识-第十八章系统对中断的处理②

嵌入式Linux应用开发-基础知识-第十八章系统对中断的处理② 第十八章 Linux系统对中断的处理 ②18.3 Linux中断系统中的重要数据结构18.3.1 irq_desc数组18.3.2 irqaction结构体18.3.3 irq_data结构体18.3.4 irq_domain结构体18.3.5 irq_chip结构体 18.4 在设备树中指定中断_在…

【Python】返回指定时间对应的时间戳

使用模块datetime&#xff0c;附赠一个没啥用的“时间推算”功能(获取n天后对应的时间 代码&#xff1a; import datetimedef GetTimestamp(year,month,day,hour,minute,second,*,relativeNone,timezoneNone):#返回指定时间戳。指定relative时进行时间推算"""根…

前端开发网站推荐

每个人都会遇见那么一个人&#xff0c;永远无法忘却&#xff0c;也永远不能拥有。 以下是一些可以用来查找和比较前端框架的推荐网站&#xff1a; JavaScript框架比较&#xff1a; 这些网站提供了对不同JavaScript框架和库的详细比较和评估。 JavaScripting: 提供了大量的JavaS…

深入了解 Linux 中的 AWK 命令:文本处理的瑞士军刀

简介 在Linux和Unix操作系统中&#xff0c;文本处理是一个常见的任务。AWK命令是一个强大的文本处理工具&#xff0c;专门进行文本截取和分析&#xff0c;它允许你在文本文件中查找、过滤、处理和格式化数据。本文将深入介绍Linux中的AWK命令&#xff0c;让你了解其基本用法和…

Cesium实现动态旋转四棱锥(2023.9.11)

Cesium实现动态悬浮旋转四棱锥效果 2023.9.11 1、引言2、两种实现思路介绍2.1 思路一&#xff1a;添加已有的四棱锥&#xff08;金字塔&#xff09;模型实现&#xff08;简单但受限&#xff09;2.2 思路二&#xff1a;自定义四棱锥几何模型实现&#xff08;复杂且灵活&#xff…

双指针算法——复写零

双指针算法——复写零&#x1f60e; 前言&#x1f64c;复写零板书分析&#xff1a;解题代码&#xff1a;B站视频讲解 总结撒花&#x1f49e; &#x1f60e;博客昵称&#xff1a;博客小梦 &#x1f60a;最喜欢的座右铭&#xff1a;全神贯注的上吧&#xff01;&#xff01;&#…

VUE3照本宣科——认识VUE3

VUE3照本宣科——认识VUE3 前言一、命令创建项目1.中文官网2.菜鸟教程 二、VUE3项目目录结构1.public2.src&#xff08;1&#xff09;assets&#xff08;2&#xff09;components 3. .eslintrc.cjs4. .gitignore5. .prettierrc.json6.index.html7.package.json8.README.md9.vit…