【面向对象课程设计】第二单元电梯优化

【面向对象】第二单元电梯优化


优化,从本次实际测试中的一些尴尬情况说起

一条请求需要分段执行时

我在事后咨询了一下身边同学的一些设计,由于一条指令可能无法被一个电梯一次性执行,所以有不少同学和我采用了相同的设计,我们将一条指令拆分成两条(在本次作业中,根据给定的电梯可停靠楼层,可以保证一条指令至少被拆分成两条就一定可以完成),然后重新封装成一个指令类,主要包含两个成员变量,其中一项mainRequest,相当于当前需要执行的指令,另一项是一个队列,保存执行完mainRequest之后要执行的指令。这样是为了确保不会出现明明一个人还没有到达楼层就被接走的顺序问题。就像下面这样:

public class SplitRequest {
    private PersonRequest mainReq;
    private LinkedList<PersonRequest> subReq;
    ......
    public void updateMainReq() {
        // use this func when finish the cur mainReq
        if (subReq.isEmpty()) {
            mainReq = null;
        } else {
            mainReq = subReq.remove();
        }
    }
}

在随机测试中这就有可能出现很多的性能问题,比如,当一个电梯(记为M)正在执行mainReq时,接下来要执行下一条指令(subReq)的电梯(记为N)明明没有任务,但是却在等待,等到此电梯按照mainReq将人送达后,才去接人,这样就浪费了很多时间。其实如果N空闲的话,完全可以在M将人送达到指定楼层之前,提前去等待,但是按照这种设计是做不到的,因为我们不知道什么时候M能将人送到,如果M还没送到,N就输出已经接上人并离开了,这就出错了。

这种时间浪费的原因在于,等待接手子任务的电梯无法预知未来,下一条指令对于该电梯并不是透明的。下面,我们重新修改结构,让电梯预知未来。

(下面的思路借鉴于宿舍对门的hqy巨佬,设计的初衷是为了让电梯能够更方便的安全停止,此处稍作调整借来一用)

  • 结构调整

    我们调整一下结构。我们为每一个电梯分配两段队列(在本次作业中是两段,因为分解成至少两条指令一定可以完成执行,至于更一般的情况,稍后会讨论),第一段,用于存储当前等待执行的指令(优先级最高的指令),第二段存储未来会执行的指令(相对低优先级)。每次调度器获取一条请求,如果能直接被执行,则放到对应电梯里的一段队列中,如果不可以,对指令进行拆分(拆分方法稍后会讨论),其中一条放入一部电梯的一段队列中,另一条放入另一部电梯的二段队列中。

    同时为每一层开辟一个等待队列,记录这到达这一层并且等待换乘的人。

  • 保证时序正确

    接下来,我们先要保证最基本的问题,不能出现顺序的紊乱。当一部电梯为一段队列为空时,开始对二段队列进行查找。如果二段队列为空,则wait,如果二段队列有指令,我们选择一条合适的指令(选择方法稍后讨论),并让电梯前往该指令的出发楼层等待。如果电梯到达楼层,就遍历这一层的等待队列,检查此人是否已经到达。如果没有则视情况选择是否继续等待(稍后讨论),如果到达,接上人,完成这条指令。

  • 进一步的细节讨论

    • 如何从二段队列选择合适指令

      首先我们要做一步操作,如果一条一段队列指令已经送达并且释放出了二段指令,那么遍历其他电梯的二段队列,如果发现这一项,则将其直接转存到对应电梯的一段队列中,因为此时这条指令已经不需要预知,它是实实在在可以被执行的。同时,这样就使得他们可能被捎带,节省了专门去接的时间。

      这样,我们保证了二段队列中的指令一定是还没有到达的。这时,当一部电梯想要查找合适的二段队列指令时,我认为比较好的是让两部进行指令交接的电梯之间相互等待时间尽可能小,而如前文所述,设电梯当前执行指令到达时间为t1,另一部电梯提前迎接的时间到达时间为t2,我们要找满足t1>=t2的条件下t1-t2尽可能小的二段队列指令。

    • 如果此时去迎接二段队列指令的电梯在迎接过程中或者在到达并等待时一段指令队列突然被加入指令

      这时如何判断是继续迎接,还是去执行一段指令队列的指令?如果这段指令可以被二段指令捎带,那么继续等待,如果不能,判断当前还需要的等待时间以及去完成这条一段指令的时间,根据时间衡量是否继续等待。(其实在这里我个人感觉不太方便将每一种情况一一列举,我这样做的初衷,就是希望不要让一部电梯执行一段指令时另一部明明没有工作的电梯闲着,所以与其在这里讨论的太细,不如直接设定优先级,规定第一段指令优先执行,但是这样就有可能造成电梯白白上去迎接二段队列指令,因此,折中一下,可以在迎接完这条二段指令并将其送达后,继续执行一段指令,即一段指令优先级高,但是不会打断二段指令的执行

如何拆分请求

上面我们讨论了拆分请求之后的一些操作,那么如何拆分请求?

首先,我想先谈谈对选择楼层停靠的电梯的直观感受。我感觉这有点像离散数学中讲到的图的概念,这个图的大致形状是三条线,每一条线代表一个电梯,线上的节点即该电梯可以停靠的楼层,节点之间的边的权重即为这部电梯从一个楼层到另一个楼层用的时间。此外,三条线彼此之间也是存在边的,如果两部电梯之间有共同可以停靠的楼层,则在这两个节点中间增加一条边,这条边的权重为0,因为在同一层运动不会花费时间。

比如,我们有一个5层楼,A电梯可以停靠1、2、4、5,B电梯可以停靠2、3、4,A电梯一层1s,B电梯一层0.5s,如果不计开关门事件,那么这个图大致是下面这样的:

这样,要想合理拆分请求,从表面上看,就是求这个加权无向图上两个点的最短路径。至于求解加权无向图的最短路径,只要使用在数据结构中学习的Dijkstra算法即可。

然而在其中还存在一些问题。首先,关于起始点和目的地,如果这一层正好有多个电梯可以停靠,这时我想对所有情况进行遍历,查找最短路径,因为电梯数量不是很大,就算起始和结束都是3个电梯都可以停靠,那么总共只需要遍历9次。这样,我们就能精确地找到一条路径,根据这条路径,我们可以精确地为电梯指派一段任务和二段任务。

然而实际上问题并不会这么简单,因为我们现在只考虑了理论上的最短到达时间,然而在实际调度时,我们还要再考虑两件事。第一件是换乘电梯时的开关门时间,判断这样换乘是否值得,比如上面的图,如果从A换乘B,中间经历的换乘时间与在B中运行的时间之和超过了2s,那么完全换乘反而会造成性能损失。所以,针对上面的图,我们要再进行修改,即为链接相同楼层的边,例如A2与B2增加权重,这个权重应当是一个开门时间与一个关门时间的和。第二件,就是这条路径可以理论上让时间缩短,但是我们不知道现在电梯的位置,如果要想能够更加理想,我们应该能够在找最短路径的基础上结合上当时电梯的位置。即使使用最暴力的手段,根据当前电梯位置、可能路径遍历每一种可能情况,这样一方面会造成很大的开销,另一方面,这样仍然不能结合当前电梯内部的指令,仍有没有讨论的情况。在随机的情况下,有时讨论的过于细,有可能不仅会造成性能的开销,还会反而造成平均性能的下降。因此,我觉得到这里,我们就将路径进行分配即可,不必太过于追究细节。

最后,关于针对如何将数据结构封装,我认为在这次任务中可以构造一个Graph类,大致框架如下:

public class Graph {
  private static final Graph graph = new Graph();   // for the info of ele is setted
  public Graph() {
    // use the information in scheduler and setup the graph
  }
  public Graph getInstance(){
    return graph;
  }
  public SplitRequest getBestReq(){
    LinkedList<Node> road = Dijkstra(from, to);
    // return the "best" with the result of road
  }
  private LinkedList<Node> Dijkstra(int from, int to){
    // return road nodes
  }
}

由于电梯的状态已经确定,所以对于图这个类,仍可以使用单例模式。这样,我们在初始化的时候只要同时对这个类里的对象同时进行初始化,然后在需要查找请求时调用上面的getBestReq()方法,直接得到调度后的指令,就可以不必关注具体细节,从而实现将数据结构整合到我们的程序中。

针对随机情况整体性能的平衡

在这次调度任务中,如果不做任何调度,B电梯的任务会变的重,这样就会导致三个电梯负载不平均,进而会导致整体效率的下降。正如讨论区大佬所言,为了实现更合理的调度,应当记录每一个电梯的运行负载状态,根据这个状态进行分配。我想在这里,结合我上面所述的方法,浅谈一下这个“负载状态”的数据记录可能用法。

在上一节,可以看到有时从一个楼层到另一个楼层可以有多条路径,之前所述的方法是找这几条路径中最短的作为最终选定的最短路径。现在,我们可以为其设置一个阀值,当路径与最短路径的差值小于这个阀值,我们都可以将其作为备选路线。此时,我们看三个电梯的负载压力,从这些被选路径中,挑选一条最占用压力最小的电梯的路径,并将其分配。

以上是我对可能的优化的一点构想,暂时还没有实现,也一定会有很多问题,很多考虑不周的地方,甚至还会有不利于优化的地方,希望大家指正。

猜你喜欢

转载自www.cnblogs.com/realmagicjim/p/10738411.html