《编程之美》学习笔记

电梯调度算法

基于电梯调度优化问题,首先根据问题建立适当的模型,然后求解模型以及进行代码实现,参考于《编程之美》中小飞的电梯调度算法一节内容。

问题描述

所有乘客在从第一层上电梯并选择自己的目的层,到达电梯自动计算的应停楼层后电梯停止,所有乘客继续爬楼梯到目的层。假设往上爬一楼层消耗k单位能量,往下走消耗1单位能量。那么,电梯停在哪一层可保证这次乘坐电梯的所有乘客爬楼梯消耗的能量和最小。

模型建立

寻找影响因数

乘客数量,需停的目的楼层。

建立优化模型

假设楼层共N层,电梯停在第x层,要去第i层的乘客数量为Numbers[i],则爬楼梯消耗的能量和为

i=1N(Numbers[i]|ix|), 因此,需找到一个整数x,使得其值最小。

模型求解

假设电梯停在第i层,设 N1 为第i层以下的乘客数, N2 为第i层的乘客数, N3 为第i层以上的乘客数, Yi 为乘客爬楼梯消耗的能量和,则电梯改停在i-1层时,

Yi1=Yi(N1N2N3k), 电梯改停在i+1层时,

Yi+1=Yi+(N1+N2N3k), 由此可见

N1+N2<N3 时,停i+1层更好。先计算停在第1层时的

N1N2N3 的值,按照上述规则遍历一次可得

nMinEnergy=Yi,

nTargetFloor=i.

代码实现

/** * * o(N) */
int passengerNumbers[]; //passengerNumbers[i]表示到第I层的乘客数量
int nMinEnergy, nTargetFloor;
int N1, N2, N3;

nTargetFloor = 1;
nMinFloor = 0;
for(N1=0, N2=passengerNumbers[1], N3=0, i=2; i<=N; i++) {
    N3 += passengerNumbers[i];
    nMinEnergy += passengerNumbers[i]*(i-1)*k;
}
for(i=2; i<=N; i++) {
    if(N1+N2<N3){
        nTargetFloor = i;
        nMinEnergy += (N1+N2-N3*k);
        N1 += N2;
        N2 = passengerNumbers[i];
        N3 -= passengerNumbers[i];
    } else 
        break;
}
return (nTargetFloor, nMinEnergy);

精确表达浮点数

在计算机中使用float或double来存储小数是不能得到精确值,为得到精确计算结果,这里以分数形式来表示有限小数或无限不循环小数,参考于《编程之美》中精确表达浮点数一节内容。

思路分析

对于一般情况下的循环小数如

Z=0.a1a2...an(b1b2...bm) 可知

10nZ=a1a2...an+Y=a1a2...an+b1b2...bm/(10m1)


Z=(10m1)a1a2...an+b1b2...bm(10m1)10n

最后进行约分即可得到结果

AB=A/GCD(A,B)B/GCD(A,B)

算法实现

#include <iostream>
#include <string>
#include <algorithm>
using namespace std; 

int gcd(int a, int b)
{
    return (b == 0) ? a : gcd(b, a%b); 
}

int tenPow(int n)
{
    int ret = 1; 
    for (int i = 0; i < n; i++)
    {
        ret *= 10; 
    }
    return ret; 
}

string intToString(int num)
{
    string ret; 
    if (num == 0) num += '0'; 
    while (num)
    {
        ret += num%10 + '0'; 
        num /= 10; 
    }
    reverse(ret.begin(), ret.end()); 
    return ret; 
}

class Fraction
{
private:
    int intPart; // 整数部分
    int decPart; // 循环节前小数部分
    int cntDec;  // 循环节前小数部分的位数
    bool isCyc;  // 是否有循环节
    int cycPart; // 循环节部分
    int cntCyc;  // 循环节部分的位数

public: 
    Fraction() // 默认构造函数
    {
        intPart = 0; 
        decPart = 0; 
        cntDec = 0; 
        isCyc = false; 
        cycPart = 0; 
        cntCyc = 0; 
    }

    Fraction(string str)
    {
        intPart = 0; 
        decPart = 0; 
        cntDec = 0; 
        isCyc = false; 
        cycPart = 0; 
        cntCyc = 0; 
        int i = 0, len = str.size(); 
        while (i < len && str[i] != '.') // 计算整数部分
        {
            intPart = intPart * 10 + str[i] - '0'; 
            i++; 
        }
        if (i < len) // 小数部分存在
        {
            i++; 
            while (i < len && str[i] != '(') // 计算循环体前小数部分
            {
                decPart = decPart * 10 + str[i] - '0'; 
                i++; 
                cntDec++; 
            }
            if (i < len) // 循环体存在
            {
                i++; 
                isCyc = true; 
                while (i < len && str[i] != ')')
                {
                    cycPart = cycPart * 10 + str[i] - '0'; 
                    i++; 
                    cntCyc++; 
                }
            }
        }
    }

    string toString()
    {
        string ret; 
        int numerator = 0, denominator = 0; 
        if (isCyc) 
        {
            denominator = tenPow(cntDec) * (tenPow(cntCyc) - 1); 
            numerator = decPart * (tenPow(cntCyc) - 1) + cycPart; 
        }
        else 
        {
            denominator = tenPow(cntDec); 
            numerator = decPart; 
        }
        int g = gcd(numerator, denominator); // 约分 
        numerator /= g; 
        denominator /= g; 
        numerator += denominator * intPart; 
        ret = intToString(numerator) + " / " + intToString(denominator); 
        return ret; 
    }
}; 

int main(int argc, char *argv[])
{
    string str; 
    while (cin >> str)
    {
        Fraction fr(str); 
        cout << fr.toString() << endl;  
    }
    return 0; 
}
点赞