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

news2024/11/28 7:31:11

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

这里写目录标题

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

人员排班问题

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

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

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

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

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

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

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

问题描述

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

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

数学建模

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

编程求解(ortools+JavaAPI)

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

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

import com.google.ortools.Loader;
import com.google.ortools.sat.*;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVRecord;

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

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

    public int getDemandOfEmployees(int day, int shift) {
        return demandOfEmployees[day][shift];
    }

    public EmployeeSchedulingProblem() 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[][] demandOfEmps = 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));   // 晚班需要员工的数量

                //保存至二维数组,某天某班次需要的员工数量
                demandOfEmps[day][0] = morningShiftEmpNum;
                demandOfEmps[day][1] = middleShiftEmpNum;
                demandOfEmps[day][2] = nightShiftEmpNum;
                this.n_employees += morningShiftEmpNum + middleShiftEmpNum + nightShiftEmpNum;
            }
            this.n_employees = (int) Math.ceil((double) (this.n_employees) / 5) + 1;
        } catch (IOException e) {
            logger.info(e.getMessage());
        }

        return demandOfEmps;
    }

    public void orToolssolve() {
        Loader.loadNativeLibraries();

        // 声明模型
        CpModel model = new CpModel();
        // 初始化决策变量
        Literal[][][] x = new Literal[n_employees][n_days][n_shifts];

        for (int n : employees) {
            for (int d : days) {
                for (int s : shifts) {
                    x[n][d][s] = model.newBoolVar("shifts_n" + n + "d" + d + "s" + s);
                }
            }
        }

        // 约束:每天各个班次在岗的人数符合需求
        for (int day = 0; day < days.length; day++) {
            for (int shift = 0; shift < shifts.length; shift++) {
                LinearExprBuilder numShiftsWorked = LinearExpr.newBuilder();

                for (int empNum = 0; empNum < n_employees; empNum++) {
                    numShiftsWorked.add(x[empNum][day][shift]);
                }
                model.addLinearConstraint(numShiftsWorked, this.getDemandOfEmployees(day, shift), n_employees);
            }
        }
        // 约束:每人每天最多只有一个班次
        for (int n : employees) {
            for (int d : days) {
                List<Literal> work = new ArrayList<>();
                for (int s : shifts) {
                    work.add(x[n][d][s]);
                }
                model.addAtMostOne(work);
            }
        }
        // 约束:前一天是晚班的,第二天不能是早班
        for (int e : employees) {
            for (int d : days) {
                List<Literal> work = new ArrayList<>();
                work.add(x[e][d][2]);
                if (d == 6) {
                    work.add(x[e][0][0]);
                } else {
                    work.add(x[e][d + 1][0]);
                }

                model.addAtMostOne(work);
            }
        }
        // 约束:一周工作工作时间不能超过5天
        for (int empNum = 0; empNum < n_employees; empNum++) {
            LinearExprBuilder expr = LinearExpr.newBuilder();
            for (int day = 0; day < days.length; day++) {
                for (int shift = 0; shift < shifts.length; shift++) {
                    expr.add(x[empNum][day][shift]);
                }
            }
            model.addLinearConstraint(expr, 0, 5);
        }

        // 目标:雇佣的员工最少,即有排班的班次总数最少
        LinearExprBuilder obj = LinearExpr.newBuilder();
        for (int n : employees) {
            for (int d : days) {
                for (int s : shifts) {
                    obj.add(x[n][d][s]);
                }
            }
        }
        model.minimize(obj);

        // 求解
        CpSolver solver = new CpSolver();
        CpSolverStatus status = solver.solve(model);

        if (status == CpSolverStatus.OPTIMAL || status == CpSolverStatus.FEASIBLE) {
            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 (solver.booleanValue(x[e][d][s])) {
                            shift = s + 1;
                            shiftCount += 1;
                        }
                    }
                    System.out.printf("%d\t", shift);
                }
                System.out.printf("员工%d这周上%d个班次", e + 1, shiftCount);
                System.out.println();
            }
        } else {
            System.out.printf("No optimal solution found !");
        }
    }

    public static void main(String[] args) throws IOException {
        EmployeeSchedulingProblem esp = new EmployeeSchedulingProblem();
        esp.orToolssolve();
    }
}

求解结果

每个员工在那一天上第几个班,本文or-tools求解结果,如图所示,如员工1-周1-上夜班,员工2-周1-不上班;0不上班、1早班、2晚班、3夜班。

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

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

相关文章

Spring(JavaEE进阶系列1)

目录 前言&#xff1a; 1.Servlet与Spring对比 2.什么是Spring 2.1什么是容器 2.2什么是IoC 2.3SpringIoC容器的理解 2.4DI依赖注入 2.5IoC与DI的区别 3.Spring项目的创建和使用 3.1正确配置Maven国内源 3.2Spring的项目创建 3.3将Bean对象存储到Spring&#xff08…

21.本地存储

目录 1 保存 wx.setStorageSync() 2 获取 wx.getStorageSync() 3 删除指定的 wx.removeStorageSync() 4 清空所有 wx.clearStorageSync() 1 保存 wx.setStorageSync() 第一个参数是键&#xff0c;第二个参数是值&#xff0c;我放在生命周期函数中了&#xff0c;所以一…

怒刷LeetCode的第17天(Java版)

目录 第一题 题目来源 题目内容 解决方法 方法一&#xff1a;过滤和排序 方法二&#xff1a;迭代 第二题 题目来源 题目内容 解决方法 方法一&#xff1a;回溯算法 方法二&#xff1a;动态规划 方法三&#xff1a;DFS剪枝 方法四&#xff1a;动态规划状态压缩 方…

基于SpringBoot的高校学科竞赛平台

目录 前言 一、技术栈 二、系统功能介绍 竞赛题库管理 竞赛信息管理 晋级名单管理 往年成绩管理 参赛申请管理 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步…

【机器学习笔记】

文章目录 1.优化器讲解 1.优化器讲解

超详细的Pycharm.2023下载与安装教程

Pycharm.2023下载与安装教程 Pycharm下载Pycharm安装 Pycharm下载 Pycharm官网下载地址&#xff1a; https://www.jetbrains.com/pycharm/download/?sectionwindows#sectionwindows 进入如下界面&#xff1a;一般分为专业版本和社区版本&#xff0c;平时做深度学习的话社区版…

Ubuntu镜像源cn.arichinve.ubuntu.com不可用原因分析和解决

文章目录 Ubuntu查看系统版本Ubuntu更新系统不能更新Ubuntu查看APT更新源配置cn.archive.ubuntu.com已经自动跳转到清华镜像站Ubuntu变更镜像源地址备份原文件批量在VIM中变更 Ubuntu国内镜像站推荐推荐阅读 今天想要在Ubuntu环境下搭建一个测试环境&#xff0c;进入Ubuntu系统…

基于51单片机NEC协议红外遥控发送接收仿真设计( proteus仿真+程序+原理图+报告+讲解视频)

基于51单片机NEC协议红外遥控发送接收仿真设计 讲解视频1.主要功能&#xff1a;2.仿真3. 程序代码4. 原理图5. 设计报告6. 设计资料内容清单&&下载链接 基于51单片机NEC协议红外遥控发送接收仿真设计 51单片机红外发送接收仿真设计( proteus仿真程序原理图报告讲解视频…

查看react内置webpack版本的方法

yarn list --pattern webpack npm ls --pattern webpack

力扣-303.区域和检索-数组不可变

Idea 需计算数组nums在下标right 和 left-1 的前缀和&#xff0c;然后计算两个前缀和的差即可。 需要注意的是&#xff0c;当left为0的时候&#xff0c;如果还是left-1则会发生数组访问越界错误。 AC Code class NumArray { public:vector<int> sum;NumArray(vector<…

嵌入式学习笔记(41)SD卡启动详解

内存和外存的区别&#xff1a;一般是把这种RAM(random access memory,随机访问存储器&#xff0c;特点是任意字节读写&#xff0c;掉电丢失)叫内存&#xff0c;把ROM&#xff08;read only memory&#xff0c;只读存储器&#xff0c;类似于Flash SD卡之类的&#xff0c;用来存储…

基于YOLOv8的安全帽检测系统

1.Yolov8介绍 Ultralytics YOLOv8是Ultralytics公司开发的YOLO目标检测和图像分割模型的最新版本。YOLOv8是一种尖端的、最先进的&#xff08;SOTA&#xff09;模型&#xff0c;它建立在先前YOLO成功基础上&#xff0c;并引入了新功能和改进&#xff0c;以进一步提升性能和灵活…

lvgl不能显示图片,但可以显示按键?

AT32F403A, IAR, ST7735S, LVGL8.3。 一、现象&#xff1a; 本来想着用LVGL做一个摄像头的显示功能 切换动态。可是死活实现不了功能。 因为移植好LVGL后&#xff0c;首先测试了显示按键&#xff0c;功能正常&#xff0c;以为是一切正常。在模拟器上调试效果完成后&#xf…

复习Day07:链表part03:21. 合并两个有序链表、2. 两数相加

之前的blog链接&#xff1a;https://blog.csdn.net/weixin_43303286/article/details/131700482?spm1001.2014.3001.5501 我用的方法是在leetcode再过一遍例题&#xff0c;明显会的就复制粘贴&#xff0c;之前没写出来就重写&#xff0c;然后从拓展题目中找题目来写。辅以Lab…

【Kafka专题】Kafka快速实战以及基本原理详解

目录 前言课程内容一、Kafka介绍1.1 MQ的作用1.2 为什么用Kafka 二、Kafka快速上手2.1 实验环境2.2 单机服务体验2.3 认识Kafka模型架构2.4 Kafka集群2.5 理解服务端的Topic、Partion和Broker2.6 章节总结&#xff1a;Kafka集群的整体结构 三、Kraft集群&#xff08;拓展&#…

SpringMVC的请求映射:路由请求的精准导航

SpringMVC的请求映射&#xff1a;路由请求的精准导航 SpringMVC是一个用于构建Web应用程序的强大框架&#xff0c;它提供了众多的特性和组件来简化开发过程。其中&#xff0c;请求映射是SpringMVC中的一个关键特性&#xff0c;用于将HTTP请求映射到具体的处理方法。本文将深入…

电池集成充电解决方案提供商【XCharge Group】获得壳牌风险投资

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 猛兽财经获悉&#xff0c;总部位于德国汉堡的电池集成充电解决方案提供商XCharge Group今日宣布已获得了壳牌风险投资公司的投资&#xff0c;这笔交易的金额没有披露。 XCharge Group计划将这笔资金用在三方面&#xff1a;…

Vue中的数据分页与分页组件设计

Vue中的数据分页与分页组件设计 在前端开发中&#xff0c;数据分页是一个常见的需求&#xff0c;特别是当处理大量数据时。Vue作为一款流行的JavaScript框架&#xff0c;提供了强大的工具和生态系统来实现数据分页。本文将介绍如何在Vue中进行数据分页&#xff0c;以及如何设计…

制作原创音乐app软件FL Studio21.2中文版

如果你正在录制、编辑或创作新歌曲&#xff0c;你会需要使用 FL Studio 快捷键。FL Studio 可用于录制、编辑和制作&#xff0c;以及专业人士录制和创作歌曲。在 FL Studio 中创建音乐专辑也是一个漫长的过程&#xff0c;可能会变得复杂且需要较长时间。很好的是&#xff0c;学…

20230224_HDR-ISP_unpack_depwl_01

https://github.com/JokerEyeAdas/HDR-ISP/tree/main 1.unpack&#xff1a;解析raw图 (1)unpack&#xff1a;2个字节1个像素 (2)mipi10&#xff1a;5个字节4个像素 [p1 9:2][p2 9:2][p3 9:2][p4 9:2][(p1 1:0)(p2 1:0)(p3 1:0)(p4 1:0)] (3)mipi12&#xff1a;3个字节2个像…