这是一次课程设计实验。
任务描述
你运行一家外卖快递服务店,负责一个区域内的外卖订单接收和餐食快递.你有一笔启动资金,可以招募外卖骑手帮你送餐,来赚取快递费。但你也会面临风险,本区域的订单你都有义务接收,不能拒绝,若拒单就会被工商部门吊销营业执照;但如果接收多个订单后,因为骑手来不及送达,导致某个订单超时未送达,客户会投诉,你会被罚款。
因此,你的任务就是制定聪明的调度策略,避免拒单被吊销营业执照(人工调度在订单高峰期可能会来不及接收),避免因为罚款而破产,并且尽可能赚更多的钱。
派送区域假设及约定
约定1: 你负责的外卖派送区域如图所示,该区域包含99的房间,每格是一个房间,既可以是下订单的食客家,也可以是接单的餐馆。
约定2: 方格之间的88条街道是骑手唯一可走的道路;骑手停在方格的上下左右街道,即算抵达。
约定3: 每个方格的宽高都一样,即骑手走过每个方格的距离一样,速度也一样,约定为骑手每走过一个方格花费1个时间单位,骑手从最左边直达最右边花费时间8个时间单位,即拐弯不花时间,经过路口不花时间。
约定4: 为了记录房间和骑手位置,坐标系定为17*17,约定左上角的房间逻辑坐标为(0,0),右下角房间的逻辑坐标为(16,16)。
运营规则及约束(1—10条)
约束-1:系统开始运营时,你有1000
; 只要你有钱,骑手数量不限;在系统运营的整个期间,你都可以随时招聘骑手,但必须有足够的钱,不能拖欠;
约束-3:你负责的外卖派送区域内,发起的任何订单都必须接收;如果订单发起后,3个时间单位内没有派单给现有骑手,则视为拒单,你将被吊销营业执照,运营终止。
约束-4:派单的方式只能是将其按顺序派给指定骑手。派单的操作可以是人工派单,也可以是程序按调度策略自动派单。人工派单的操作比较复杂,需要用鼠标逐个选中现有未处理订单,将其分配给某个骑手。
约束-5:所有骑手的初始位置必须是同一位置;但起始位置需要在你的程序中自行设定。骑手初始位置的设定可以在程序中写死,也可以在系统启动时修改设置。注意:如果骑手初始在左上角,那么立刻接到一个(右下角>左上角)的订单时,可能超时。
约束-6:每个订单从下单时间开始,要求在30个时间单位内完成服务(先后抵达餐馆和食客家),否则算超时。满30个时间点订单若没有完成结单,客户会投诉导致立刻罚款50
;
约束-8:无须考虑骑手负载限制,一位骑手可以带无限外卖;但超时未达要按约束-7条处罚;
约束-9:负债即破产!一旦破产,即刻停止运营,系统盘点每位骑手的接单数、完成数、超时数。
约束-10:系统运营期间,至少每个时间单位更新一次,显示当前钱数、每位骑手的位置、接单数、完成数、超时数、当前利润。
派单策略(基础部分)
策略x-1: 首先按下单时间顺序,将新订单放入待处理队列。
策略x-2: 根据骑手数量,将区域划分为几个子区域,分给每个骑手,注意预先保留一个跨子区域订单骑手。
策略x-3: 派单时,从待处理队列取出队首订单,判断属于哪个子区域,就分给哪个骑手,分派后此单出待处理队列,加入对应骑手的待送达队列;餐馆和食客不在一个区域的订单,分给跨子区域骑手。
策略x-4: 当跨区域骑手的待派送队列中元素超过预警值时(例如10个),再分出一个骑手作跨区域骑手,剩下骑手重新划分区域,更改仅对后续订单生效,已分配订单不受影响。注意:预警值是你自己在程序中设定的,根据经验设置。
策略x-5: 所有单派完后,开始轮流对所有骑手的待派送队列进行优化。优化策略是:取出队首订单,作为当前目标点;计算骑手当前位置和当前目标点的区域范围,然后扫描队列后续订单中的所有可达目标点(见后续解释),筛选出属于此区域内的,设计出合理的行走路线,只要按此路线当前目标点不超时,即可插入到队首目标点之前。
设计思路
贪心算法
代码实现
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<time.h>
#include<Windows.h>
#include<vector>
#include "iostream"
#include "conio.h"
#include<algorithm>
#include"string"
using namespace std;
#define MAX 1000
#define MIN 20
int map[17][17] = { 0 }; //地图标记初始化,1,2,3对应骑手 ,4餐厅 5食客
int money = 1000; //项目资金变量
int indent = 0; //订单数量标记 (总数、完成数、超实数)
int fakuan = 0;
vector<int> peisongwanc; //配送完成
vector<int >peisongweiwanc;//配送没有完成
int tme = 0, cnt = 0; //程序运行时间
int Area_value[17][17];
string fandan;//罚单记录
string wanc;//完成记录
class Rider;//声明
class INdent;//声明
vector<Rider> Vrider;//骑手
vector<INdent> Vindent;//订单
class coordinate
{
public:
int x, y;
int zhi;//根据位置计算距离 来排序
int peisongshij;//记录当前的配送时间
int dingdanbianhao;//记录着位置的订单编号
int paduancandingorkehu;//判断是餐厅 还是客户 1是餐厅 2是客户
coordinate() {
}
coordinate(int x1, int y1, int dingdanbianhao1, int paduancandingorkehu1, int peisongshij1) {
x = x1;
y = y1;
dingdanbianhao = dingdanbianhao1;
paduancandingorkehu = paduancandingorkehu1;
peisongshij = peisongshij1;
}
coordinate(int x1, int y1) {
x = x1;
y = y1;
}
//判断a2 是否在a1 周围一个
bool getpd(coordinate a1, coordinate a2) {
int d1 = abs(a2.x - a1.x);
int d2 = abs(a2.y - a1.y);
if (d1 == 0 && d2 == 1)
return true;
if (d1 == 1 && d2 == 0)
return true;
return false;
}
};
class INdent {
public:
int n; //序号
int cnt; //标记骑手
int flag; //属性标记 ,1接受未配送 2配送未完成 3完成
int q_time = 0; //下单时间,此变量用于文件读取
int restaurant[2]; //餐馆坐标
int client[2]; //顾客坐标
int shenyupeisongshijian;//剩余配送时间
};
//自定义最近的距离排序函数
bool sortFun1(const coordinate& p1, const coordinate& p2)
{
return p1.zhi < p2.zhi;//升序排列
}
bool sortFun2(const INdent& p1, const INdent& p2)
{
return p1.q_time < p2.q_time;//升序排列
}
class Rider
{
public:
int x, y;//棋手坐标
int state;//0停泊 1配送中 2没货移动返回初始地址中
vector<coordinate> vec1; //默认初始化, 需要配送的位置
int zhi;//根据位置计算距离 来排序
Rider() {//骑手初始的位置
x = 9;
y = 8;
}
//判断是否可以将这个路线添加进来 cant餐厅位置 kehu客户位置 zhuangt=1 超时30不配送 zhuangt=1超时60不配送
bool PDshifoujiatu(INdent& danzi, int standard) {
coordinate cant(coordinate(danzi.restaurant[0], danzi.restaurant[1], danzi.n, 1, tme - danzi.q_time));
coordinate kehu(coordinate(danzi.client[0], danzi.client[1], danzi.n, 2, tme - danzi.q_time));
int tujinshi = vec1.size() + 2;//当前总的途径数
int* dingdanpan = new int[vec1.size() + 2];
int i, dqx, dqy;
vector<coordinate> cunf; //存放所有位置的订单
vector<coordinate> zunfzuiyoutuj;//存放最优配送途径
vector <coordinate>::iterator it;
for (i = 0; i < tujinshi; i++) dingdanpan[i] = -1;
for (it = vec1.begin(); it != vec1.end(); it++)//进行运行事件模拟
cunf.push_back(*it);
cunf.push_back(cant), cunf.push_back(kehu);
dqx = this->x; dqy = this->y;//骑手当前坐标
while (cunf.size() > 0) {
//计算所有距离距离当前位置距离
for (it = cunf.begin(); it != cunf.end(); it++)
it->zhi = abs(it->x - dqx) + abs(it->y - dqy);
//进行排序,包括餐厅和食客位置
sort(cunf.begin(), cunf.end(), sortFun1);
for (it = cunf.begin(); it != cunf.end(); it++) {
//判断是不是餐厅
if (it->paduancandingorkehu == 1) {
zunfzuiyoutuj.push_back(*it);
dqx = it->x, dqy = it->y;//假设骑手已经到这了
for (i = 0; i < tujinshi; i++) {
if (dingdanpan[i] == -1) {
dingdanpan[i] = it->dingdanbianhao;//记录这个订单号
break;
}
}
cunf.erase(it);//删除这个位置的元素
break;
}
else {
for (i = 0; i < tujinshi; i++)if (dingdanpan[i] == it->dingdanbianhao)break;
if (i < tujinshi) {
zunfzuiyoutuj.push_back(*it);
dqx = it->x, dqy = it->y;//假设骑手已经到这了
cunf.erase(it);//删除这个位置的元素
break;
}
}
}
}
//判断这个最优值是否不会出现超时配送
dqx = this->x; dqy = this->y;
int leijijs = 0;//累计计时
int bianliang;
int value = 0;//存储可以收获的金额
for (it = zunfzuiyoutuj.begin(); it != zunfzuiyoutuj.end(); it++) {
bianliang = leijijs + abs(dqx - it->x) + abs(dqy - it->y);
bianliang = bianliang % 2 == 0 ? bianliang / 2 : bianliang / 2 + 1;
leijijs = leijijs + bianliang;
if ((leijijs + it->peisongshij) >= 30 && standard == 1)return false;
if ((leijijs + it->peisongshij) >= 60 && standard == 2)return false;//存在停运风险
dqx = it->x, dqy = it->y;
}
vec1.erase(vec1.begin(), vec1.end());
for (it = zunfzuiyoutuj.begin(); it != zunfzuiyoutuj.end(); it++)vec1.push_back(*it);
delete[]dingdanpan;
return true;
}
//添加一个单子
void ADDdanzi(INdent& danzi) {
if (state == 2)//如果该骑手正在返回初始位置的过程中
vec1.erase(vec1.begin(), vec1.end());
state = 1;
//传入餐馆坐标
vec1.push_back(coordinate(danzi.restaurant[0], danzi.restaurant[1], danzi.n, 1, tme - danzi.q_time));
//传入客户坐标
vec1.push_back(coordinate(danzi.client[0], danzi.client[1], danzi.n, 2, tme - danzi.q_time));
danzi.flag = 1;
}
//骑手派单移动
void QIsyidong() {
if (vec1.size() <= 0 && this->state == 0) {//当派单数为零时,退出运动函数
return;
}
char a[100];
string zhi;
vector <coordinate> ::iterator it;
//获取商家或者客户的位置
it = vec1.begin();
//如果在周围了 表示这单已经配送完成
if (it->getpd(*it, coordinate(x, y)) == 1) {
if (this->state == 2) {
vec1.erase(vec1.begin(), vec1.end());
this->state = 0;
return;
}
if (it->paduancandingorkehu == 2) {
sprintf(a, "%d", it->dingdanbianhao);
zhi = a;
//如果超时了
if (it->peisongshij >= 30) {
if (it->peisongshij >= 30) {
money -= 50; //罚款
fakuan = fakuan + 50;
fandan = fandan + zhi;
}
if (it->peisongshij >= 60) {
money = -99999999;//游戏结束
fandan = fandan + zhi;
}
}
else {
money += 10;
wanc = wanc + zhi;
}
peisongwanc.push_back(it->dingdanbianhao);//放入成功送单的容器
}
vec1.erase(it);//所到之地为餐厅,就消除该餐厅标记
}
if (vec1.size() <= 0 && state == 1) {//派单数为零时,退出运动函数
state = 0;
//返回初始位置
if (this->x != 7 && this->y != 9) {
vec1.push_back(coordinate(8, 8));
this->state = 2;
}
}
it = vec1.begin();
//获取商家或者客户的位置
coordinate weizhi = *it;
if (
(
(y < weizhi.y && x % 2 != 0)
&& (y + 1 == weizhi.y ? weizhi.y % 2 != 0 || abs(x - weizhi.x) == 1 : 1)
|| (y == weizhi.y && weizhi.y % 2 == 0)
) && y + 1 < 17
) {
y = y + 1;
}
else if (
(
(y > weizhi.y&& x % 2 != 0)
&& (y - 1 == weizhi.y ? weizhi.y % 2 != 0 || abs(x - weizhi.x) == 1 : 1)
|| (y == weizhi.y && weizhi.y % 2 == 0)
) && y - 1 >= 0
) {
y = y - 1;
}
else if (
(
(x < weizhi.x && y % 2 != 0)
&& (x + 1 == weizhi.x ? weizhi.x % 2 != 0 || abs(y - weizhi.y) == 1 : 1)
|| (x == weizhi.x && weizhi.x % 2 == 0)
) && x + 1 < 17
)
{
x = x + 1;
}
else if (
(
(x > weizhi.x&& y % 2 != 0)
&& (x - 1 == weizhi.x ? weizhi.x % 2 != 0 || abs(y - weizhi.y) == 1 : 1)
|| (x == weizhi.x && weizhi.x % 2 == 0)
) && x - 1 >= 0
)
{
x = x - 1;
}
for (it = vec1.begin(); it != vec1.end(); it++) {//需要送达的地方的配送时间计算
it->peisongshij = it->peisongshij + 1;
}
return;
}
};
void indent_init() //订单输入
{
INdent a;
printf("请输入订单信息(序号 餐馆坐标x 餐馆坐标y 食客坐标x 食客坐标y ):");
scanf("%d %d %d %d %d", &a.n, &a.restaurant[0],
&a.restaurant[1], &a.client[0], &a.client[1]);
a.flag = 4;
indent++;
Vindent.push_back(a);
}
void indent_init_txt() //从文件读取
{
FILE* fp;
INdent a;
fp = fopen("sales.txt", "r");
if (!fp) {
printf("文件打开失败");
return;
}
while (!feof(fp)) //检测未到结尾
{
fscanf(fp, "%d %d %d %d %d %d", &a.n, &a.q_time, &a.restaurant[0],
&a.restaurant[1], &a.client[0], &a.client[1]);
a.flag = 4;
if (feof(fp))break;
Vindent.push_back(a);
indent++;
}
sort(Vindent.begin(), Vindent.end(), sortFun2);
puts("读入完成");
fclose(fp);
}
void out_txt() //文件输出
{
if (wanc == "" && fandan == "")return;
string a;
FILE* fp;
vector <coordinate> ::iterator it1;
int i = 1;
fp = fopen("output.txt", "at+");
fprintf(fp, "时间:%d\n", tme);
fprintf(fp, "钱:%d\n", money);
fprintf(fp, "接单数:%d\n", indent);
fprintf(fp, "完成数:%d;结单:%s;\n", peisongwanc.size(), wanc.c_str());
fprintf(fp, "超时数:%d;罚单:%s;\n", peisongweiwanc.size(), fandan.c_str());
for (vector <Rider> ::iterator it = Vrider.begin(); it != Vrider.end(); it++) {
fprintf(fp, "骑手%d位置:%d,%d; 停靠:", i, it->x, it->y);
if (it->vec1.size() > 0) {
it1 = it->vec1.begin();
if (it1->paduancandingorkehu == 1) {
fprintf(fp, "餐馆 %d %d;", it1->x, it1->y);
}
if (it1->paduancandingorkehu == 2) {
fprintf(fp, "食客 %d %d;", it1->x, it1->y);
}
}
fprintf(fp, "\n");
i = i + 1;
}
fclose(fp);
}
int Time() //计时函数,返回1满一秒
{
Sleep(100); //延时1s
cnt = cnt + 1;
if (cnt % 2 == 0)
return 1;
return 0;
}
//自定义最近的骑手排序函数
bool sortFun(const Rider& p1, const Rider& p2)
{
return p1.zhi < p2.zhi;//升序排列
}
int send_t() //派发订单
{
vector <Rider> ::iterator it;
vector <INdent> ::iterator nit;
int Is_sucess = 0;//派单是否成功
for (nit = Vindent.begin(); nit != Vindent.end(); nit++) {
if (tme < nit->q_time)continue;//当前时间小于下单时间
if (nit->flag == 1)continue;
Is_sucess = 0;
//计算每个骑手距离餐馆的位置
for (it = Vrider.begin(); it != Vrider.end(); it++) {
it->zhi = abs(it->x - nit->restaurant[0]) + abs(it->y - nit->restaurant[1]);
}
//排序寻找最近的骑手
sort(Vrider.begin(), Vrider.end(), sortFun);
//开始派单
int standard = 0;
for (it = Vrider.begin(); it != Vrider.end(); it++) {
//表示这个骑手已经在配送了
if (money >= 300) standard = 1;
else standard = 2;
if (it->state == 1) {
//检查是否可以顺路
if (it->PDshifoujiatu(*nit, standard)) {
Is_sucess = 1;//派单成功
nit->flag = 1;
break;
}
}
else {
it->ADDdanzi(*nit);
Is_sucess = 1;//派单成功
nit->flag = 1;
break;
}
}
if (Is_sucess == 0 && money > 300) {//雇佣新的骑手
money = money - 300;
Rider rid;
rid.state = 1, rid.ADDdanzi(*nit);
Vrider.push_back(rid);
nit->flag = 1;
}
}
return 0;
}
//骑手派单运动
void peo_sport()
{
vector <Rider> ::iterator it;
for (it = Vrider.begin(); it != Vrider.end(); it++) {
//开始配送
if (it->state == 1 || it->state == 2)it->QIsyidong();
}
}
int check_money() //订单状态检测 ,返回0结束
{
int i;
if (money < 0) //破产返回0
{
return 0;
}
return 1;
}
void _gotoxy(int x, int y) {
COORD c;
c.X = x;
c.Y = y;
HANDLE had = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(had, 10 | 3);
SetConsoleCursorPosition(had, c); //修改当前光标的位置
}
void show() //显示函数
{
int i, j;
for (i = 0; i < 17; i++) {
for (j = 0; j < 17; j++) {
if (i % 2 == 0 && j % 2 == 0)
map[i][j] = 1;
else
map[i][j] = 0;
}
}
vector <Rider> ::iterator it;
vector <coordinate> ::iterator coord;
i = 1;
for (it = Vrider.begin(); it != Vrider.end(); it++) {//棋手遍历
map[it->y][it->x] = 3 + i;//标记骑手位置
i = i + 1;//大于等于4的都是骑手
if (it->state == 1) {//标记正在运输中的骑手需要到达的地方
for (coord = it->vec1.begin(); coord != it->vec1.end(); coord++) {//标记餐厅和客户位置
if (coord->paduancandingorkehu == 1) {
map[coord->y][coord->x] = 2;//餐厅位置
}
if (coord->paduancandingorkehu == 2) {
map[coord->y][coord->x] = 3;//客户位置
}
}
}
}
for (i = 0; i < 17; i++)//输出模拟图
{
for (j = 0; j < 17; j++)
{
_gotoxy(6 * i, j);//定光标
if (map[i][j] == 1)printf("[ ]");
if (map[i][j] == 0)printf(" ");
if (map[i][j] == 2)printf("[餐厅]");
if (map[i][j] == 3)printf("[食客]");
if (map[i][j] > 3)printf("骑手%d", map[i][j] - 3);
}
printf("\n");
}
printf("当前总钱数:%d 当前余额:%d \n", money + Vrider.size() * 300, money);
i = 1;
for (vector <Rider> ::iterator it = Vrider.begin(); it != Vrider.end(); it++) {
printf("骑手%d的位置(x,y):(%d,%d) \n", i, it->x, it->y);
i = i + 1;
}
printf("接单数:%d \n", indent);
printf("完成数:%d \n", peisongwanc.size());
printf("超时数:%d \n", peisongweiwanc.size());
printf("时间:%d \n", tme);
printf("输入选择的功能\n(1.订单输入 2.刷新屏幕):");
}
int main()
{
int i;
char cnt;
printf("系统即将启动······\n");
system("cls");
while (1)
{
show();
if (_kbhit()) {
cnt = _getch();
system("cls");
if (cnt == '1')
{
printf("请选择(1.文件输入 2.命令行输入)");
cnt = getchar();
if (cnt == '1')
indent_init_txt();
else if (cnt == '2')
indent_init();
}
system("cls");
system("cls");
}
else {
if (Time() == 1)
{
out_txt();
fandan = "";
wanc = "";
tme = tme + 1;
}
send_t();
peo_sport();
}
if (!check_money())
{
printf("破产!!!");
printf("账户余额:%d\t接单数:%d\t完成数:%d\t超时数:%d\n", money, indent, peisongwanc.size(), peisongweiwanc.size());
i = 1;
for (vector <Rider> ::iterator it = Vrider.begin(); it != Vrider.end(); it++) {
printf("骑手%d的位置(x,y):(%d,%d) 停靠:", i, it->x, it->y);
i = i + 1;
}
break;
}
}
system("cls");
}