实现简单电梯调度
GitHub:pullself
设定(1)
首先我们假设这台电梯是能够未卜先知的。也就是说这台电梯能够预先知道这5条请求。我们可以采用较为暴力的方法来实现这个最优问题。
代码行数 | 调试bug | 编码时间 |
---|---|---|
202行 | 12个 | 12h |
首先当然是先采用暴力的方法完成目标。我们可以认为电梯只会在七个地方停靠,所以我们以这七个位置为结点。
我们可以将电梯前往10楼和前往1楼都类似为一种请求。
所以就有七个请求,在输入的五个请求有且只会停靠一次。但前往1楼和10楼的次数不是固定的。
为了便于分类。设置了:
struct node
{
int time;//请求时间
int floor;//请求楼层
int dest;//请求目的地
int flag;//状态参数
node()
{
memset(this,0,sizeof(node));
}
}ask[10];
以及:
struct nod
{
int time;//运行时间
int num_10;//厢内前往10层人数
int num_1;//厢内前往1层人数
int pos;//电梯位置
nod()
{
time=-1;
num_10=0;
num_1=0;
pos=1;
}
}ele;
之前觉得搜起来还是比较容易的,后面发现各种在+1s,-1s的问题上造成一整串数据的错误。用命分析.jpg
这样分类完后再做数据处理的时候就会比较清晰了。
bug以及优化记录
前言:小的bug就不记录了,主要记录几个关键性的。
(1)搜索边界:原本认为是将五个请求应答完毕后就到达边界,后面才明白应该在所有人的状态参数都为已出厢并且五个请求都已应答才行。被自己蠢到
(2)电梯运行时间计算:原本认为电梯运行就停靠以及运行的时间,所以在应答请求的的时候只考虑了停靠已经移动的时间。实际上在未卜先知的条件下,还有可能在预备应答某请求时,该请求还未发出。加入判断。
(3)出厢操作:由于在到达第一层或第十层的时候,需要将电梯内所有在该层出厢的人全部扔出电梯。但他们的统一状态参数都相同,所以在回溯的过程中会出现无法判断是哪一次的出厢操作出的电梯。所以用了比较蠢的a[5]
[1]来记录这次被扔出去的家伙。
(4)等待时间的计算(优化):原来想在每一次的停靠中去顺便计算wait
[2],但是实际操作中需要不断的去检查每一个请求的当前状态然后运算,很麻烦。后面发现只需要在该请求的状态被更新为已出厢的时候去计算就行了(电梯运行时间-请求时间==该请求等待时间)
(5)停靠的+1s:就是这个+1s该在哪里加的问题,后面决定在函数开始加,电梯初始运行时间就变为-1了。
(6)同楼层进入多人:在之前发现如果两次执行都在同楼层会多+1s,改进后加入前后策略的判定,解决了这一秒的多余。
(7)啊哈,又是这个1s问题:突然发现在1层以及10层的时候,有可能会同时进同时出,还需要多去掉这里多出的等待一秒。
其实还遇到很蠢的==
写成=
然后找了一大圈,插了一堆测试点。被自己蠢哭了。
(新版本咕咕了!)
设定(2)
我们知道现实中的电梯无法做到未卜先知,所以如果要尽可能使所有乘客等待总时长最短,只能做到局部最优。
说实话这种电梯算法也不合理,因为有可能出现某个人的等待时间占据了所有人等待时间的50%以上。
代码行数 | 调试bug | 编码时间 |
---|---|---|
341行 | ∞个 | 16h |
323行 | ?个 | ?h |
原版
还是属于一个半成品,还未添加输出语句,只是针对各函数进行了测试。感谢设定(1)提供的技术铺垫。
程序由四个主要的函数组成:
int main(void)
:主函数。int cre(void)
:构造函数,创建用于搜索的请求队列。有一个返回值为队列长度。void dfs(int n,int m)
:搜索函数,核心函数之一,主要功能是搜索出当前未执行的请求中的最优解。由设定(1)中的函数改造而成。void move(int y)
:移动函数:核心函数之一,负责电梯状态的更新以及等待时间的计算。
主要思想就是逐层模拟电梯运行,当接收到新请求的时候再计算进而改变策略。
bug以及优化记录
(1)初始化问题:由于搜索函数由上一个代码改造,但是忘了前者只需要做一次,而这里的搜索需要做多次。所以需要进行变量的暂存和初始化……
(2)判断混乱:在没有cre函数之前,搜索的对象是直接来自于输出,但是在不断的重复搜索过程中,会破坏掉输入的变量,所以通过cre函数创建了搜索队列。一直更新。
(3)无法确定搜索队列:如果一个人已经上了电梯,但是更新了info
[3],却没有更新队列,就会导致无法确认下一次的搜索是否需要包括这位薛定谔选手。emmmm……
int cre()
{
int n=0;
memset(ask,0,sizeof(node)*10);
for(int i=0;i<5;i++)
{
if(info[i].flag==1)
{
ask[n]=info[i];
ask[n++].mark=i;
}
}
return n;
}
(4)策略保存:一直在策略保存的问题上面出问题,在写move函数的时候一直发现保存的策略与info
已经失去了联系。您所拨打的电话是空号,导致无法准确的更新info
的状态,后续就无法进行……
(5)等待时间计算问题:之前不小心放在搜索中计算了,后面整合到move函数中计算了。死循环还是美滋滋啊……
(6)断网了,快没流量了:这个问题解决不了哇!!!!!!!
重装升级版
啊哈,经过重修改造,将各函数功能细化,推出最终版本。一晚上脑子都是一个没有门,还有五个爪子,0.5s伸出爪子抓人,0.5s收回爪子还能掉头不减速的鬼畜电梯,好想配图。
全代码由8个函数组成:
- dfs类:有原版dfs改造;
void sorting0(int m)
:全排列可执行的请求void sprting1(int n)
:在全排列中插入前往10与1的组成搜索全排列。void sortrequest(void)
:创造排序搜索队列。取代cre()函数。void caltime(void)
:在搜索中计算时间与请求状态更新。void mintime(void)
:记录当前最优策略。
- move类:由原版move改造;
int enter(void)
:判断有没人进入,以及可处理请求的删除。返回值为1
或0
代表有人或没人。int exit0(void)
:判断是否有人出去,以及计算等待时间。返回值为1
或0
代表有人或没人。int end(void)
:判断是否所有人都被扔出。
经过自检程序10万组的测试,暂时没有问题。
贴个主函数:
int main(void){
int i = 0;
int j = 0;
int time0 = -1;
int booleanew = 0;//是否有请求更新
eleStat.position = 1;
for(i = 0; i < 5; i++){
cin>>info[i].asktime>>info[i].startfloor>>info[i].destination;
}
while(end() == 0){
for(i = 0; i < 5; i++){
if(info[i].asktime <= eleStat.timeworked && info[i].asktime > time0){
eleStat.requestion[eleStat.arp] = info[i].startfloor;
eleStat.arp ++;
booleanew = 1;
}
}//是不是所有人都出来,还没全出来时进行。
time0 = eleStat.timeworked;
if(booleanew == 1){
sortrequest();
for(i = eleStat.arp - 1; i > 0; i--){
if(eleStat.requestion[i] == eleStat.requestion[i - 1]){
for(j = i; j < eleStat.arp - 1; j++){
eleStat.requestion[j] = eleStat.requestion[j + 1];
}
eleStat.requestion[eleStat.arp - 1] = 0;
eleStat.arp --;
}
}
stimulation.minwaitingtime = 10000;
sorting0(0);
booleanew = 0;
for(i = 0; i < 17; i++){
stimulation.sollution[i] = stimulation.sollutionmin[i];
}
}
//电梯运行的模拟
if(eleStat.position != stimulation.sollution[0]){
if(stimulation.sollution[0] == 0){
eleStat.direction = 0;
}
else if(stimulation.sollution[0] > eleStat.position){
eleStat.direction = 1;
}
else{
eleStat.direction = -1;
}
eleStat.position += eleStat.direction;
eleStat.timeworked ++;
}
//如果有人出或有人进
if(enter() + exit0() != 0){
cout<<eleStat.timeworked<<"时,停靠在"<<eleStat.position<<"楼"<<endl;
eleStat.timeworked ++;
}
if(eleStat.position == stimulation.sollution[0]){
for(i = 0; i < 16; i++){
stimulation.sollution[i] = stimulation.sollution[i + 1];
}
stimulation.sollution[16] = 0;
}
}
cout<<eleStat.waitingtime;
return 0;
}
贴个自检程序:
int main(void){
int i = 0;
int j = 0;
int k = 0;
struct setinfo binfo[5] = {{0}};
struct seteleStat beleStat = {0};
struct setstimulation bstimulation = {0};
srand((unsigned int)time(NULL));
for(i = 0; i < 5; i++){
for(k = 0; k < 5; k++){
info[k] = binfo[k];
}
eleStat = beleStat;
stimulation = bstimulation;
for(j = 0; j < 5; j++){
do{
info[j].asktime = rand() % 101;
info[j].destination = rand() % 2;
info[j].startfloor = rand() % 10 + 1;
}while(info[j].asktime == 101 || info[j].destination == 2 || info[j].startfloor == 11 || (info[j].destination == 1 && info[j].startfloor == 1) || (info[j].destination == 0 && info[j].startfloor == 10));
printf("%d %d %d
",info[j].asktime, info[j].startfloor, info[j].destination);
}
main0();
}
return 0;
}
bug以及优化记录(emmmmm)
(1)当电梯在x时刻到达m楼并停留,则若在x+1时刻有在m楼的申请,程序无限循环。
原因:main()中对stimulation.sollution[]的处理在(enter() + exit0() != 0)的判断中,在一次main()中的循环中存在+2s的情况,此时x+1s发出的申请可被enter()受理,但会在下次main()中的循环中增到eleStat.requestion[]中,而再进行一次stimulation。而此次循环中enter()返回值为0,无法进行关于stimulation.sollution[]的处理。
解决办法:将main()中对stimulation.sollution[]的处理移除判断语句。
爽到
理论升级版
(1)由于时间问题,只能给出理论版本。灵感来自于设定(1)的过程,由于在之前的算法中采用的局部最优的思维,但是如果有动态规划过的话,就会发现在后续的操作过后,之前的局部最优就不再是那时候最优的选择。只有在其后接受到的新请求已经在之前请求全部应答并处理完毕时的情况下,二者才相同。
所以我们借助未卜先知的思想,在每一次的搜索过程中继续演绎后续的运动,在搜索过程中加入新请求加入的结构。如果说之前的算法是在接受信息中被动反应,这种算法就是在信息处理中主动出击。
这样理论上在第一次搜索中,就能在多层搜索中找出理论最优解。
缺点也很明显:需要演绎出几乎所有的结果,时间复杂度很高。
改造方案:将move类与dfs类组合拆分后重新构造。
(2)其实还有一种思维,加入某些特殊情况的演绎判定,改造难度低,影响度高,但是在得到最优解的情况下不如上者。
测试数据
前言:三组数据为一类,共计五类。数据范围均为int,不存在同一层进同一层出还非要进电梯的搞事二五仔。
第一类:同时间请求(包含0时刻与非0时刻,以及当请求楼层为1时)
0 1 0 1 1 0 1 2 0
0 1 0 1 1 0 1 3 1
0 1 0 1 1 0 1 4 0
0 1 0 1 1 0 1 5 1
0 1 0 1 1 0 1 6 0
第二类:同楼层请求()
1 2 0 1 2 0 2 3 0
1 2 0 3 2 1 4 3 1
1 2 0 5 2 0 6 3 0
1 2 0 7 2 1 8 8 1
1 2 0 9 2 0 10 8 0
第三类:同目的地请求()
5 2 0 1 9 1 2 3 0
12 4 0 3 4 1 4 6 0
9 9 0 5 5 1 6 3 0
21 3 0 7 2 1 8 8 0
14 4 0 9 6 1 10 10 0
第四类:大数据或者小数据()
25545 5 0 3214 1 0 354 5 1
43545 7 1 54654 1 0 3465 4 1
15456 6 0 546 1 0 687 7 0
2135 9 0 5623 1 0 56787 5 1
54654 5 0 6354 1 0 78799 1 0
第五类:随机类()
2 7 0 12 5 1 54765 8 1
3 8 1 47 3 0 14 2 0
5 5 1 7 9 0 545 9 1
3 9 0 354 4 1 4654 4 1
9 1 0 54 7 0 499 1 0
经过测试,都能合理运行。
尾言
其实从上至下,正好是一个从简至繁,又化繁回简的过程。未卜先知的代码与思想与非未卜先知的电梯思想看似毫无关系,但是实际上,前者不仅仅可以提供一个测试环境,甚至影响到了后续的思维。在非预知的环境下想要获得最优不就是要尽可能的在已知条件中达到未卜先知吗?而且在部分测试数据中二者也是有所交集的。完全可以说是前者的铺垫堆砌出了后者吧。