看到一个帖子《excel吧-找出运输收益最大的路线》,根据表中的起点和终点构成的路线,在一定的本金和时间限制下,获取收益最大的运输路线
-
采用类似《Excel·VBA数组排列函数》,尾数循环结束后,对之前的位置进位的方式,获取遍历所有可能路线,最终得到收益最大的运输路线
-
以下代码中内层
第1个do循环
的作用是,路线一直向前延伸,直到剩余时间不足;内层第2个do循环
的作用是,在一条路线延伸到底后,对最后一个起点的其他终点进行计算,判断是否为收益更大的路线;内层第3个do循环
的作用是,当一条路线延伸到底后,且路线的最后一个终点是其起点的最后一个终点时,对其起点进行递增为下一个起点,且如果路线上的终点都为其起点的最后一个终点时,一直向前递增 -
以下代码运用了
Match
函数《Excel·VBA多行多列数据简单汇总》,可查找数值在数组中的位置,返回的index从1开始计数,而在以下代码中查找的都是字典键数组从0开始计数,因此代码中For i = y To UBound(dk)
就是从被查找的字符的下一个位置开始循环(VBA字典键顺序为写入字典的顺序)
Sub 运输收益最大路线()
'res数组记录输出结果共100行3列(总收益,剩余时间,路线),b记录当前路线,mrr记录每次终点的金额,trr记录剩余时间
Dim arr, mm, tt, min_t, dict As Object, res(1 To 100, 1 To 3), r&, i&, j&, x&, y&, s$, m, t
arr = [a1].CurrentRegion: mm = 50000: tt = 1200 '初始总本金、总耗时
min_t = WorksheetFunction.Min(Application.index(arr, , 5)) '最小耗时
Set dict = CreateObject("scripting.dictionary"): tm = Timer
res(1, 1) = "总收益": res(1, 2) = "剩余时间": res(1, 3) = "路线": res(2, 1) = 0
For i = 2 To UBound(arr) '字典嵌套字典,记录起止点对应(单位成本,单位收益,耗时)
If Not dict.Exists(arr(i, 1)) Then Set dict(arr(i, 1)) = CreateObject("scripting.dictionary")
dict(arr(i, 1))(arr(i, 2)) = Array(arr(i, 3), arr(i, 4) - arr(i, 3), arr(i, 5))
Next
'初始数据,b路线数组,mrr本金数组,trr剩余时间数组
Dim b(1 To 20), mrr(1 To 20), trr(1 To 20), dk, k1, k2, k3, dj, ds, dt, change As Boolean
dk = dict.Keys: b(1) = dk(0): mrr(1) = mm: trr(1) = tt: x = 1
Do
Do While trr(x) >= min_t '线路递增
k1 = b(x): m = mrr(x): t = trr(x): dk = dict(k1).Keys '初始状态,m当前本金,t当前剩余时间
For i = 0 To UBound(dk)
k2 = dk(i): dt = dict(k1)(k2)(2) '单位耗时
If t >= dt Then
x = x + 1: b(x) = k2
dj = dict(b(x - 1))(b(x))(0): ds = dict(b(x - 1))(b(x))(1) '单价、单收益
m = m + Int(m / dj) * ds: mrr(x) = m: trr(x) = t - dt '本金递增、时间递减
Exit For
Else
If i = UBound(dk) Then Exit Do '所有终点所需时间都超过剩余耗时,跳出
End If
Next
Loop
s = ""
For i = 1 To x '路线结果
s = s & "-" & b(i)
Next
If trr(x) >= 0 Then
m = mrr(x) - mm '总收益
If m > res(2, 1) Then '写入输出数组
r = 2: res(r, 1) = m: res(r, 2) = trr(x): res(r, 3) = Mid(s, 2)
ElseIf m = res(2, 1) Then
r = r + 1: res(r, 1) = m: res(r, 2) = trr(x): res(r, 3) = Mid(s, 2)
End If
End If
Do '线路终点迭代
change = False: k1 = b(x - 1): k2 = b(x): t = trr(x - 1) '原起止点、时间复位
dk = dict(k1).Keys: y = Application.Match(k2, dk, 0) '查找k2出现位置,从1开始计数
For i = y To UBound(dk) '从k2出现位置之后开始循环
k3 = dk(i): dt = dict(k1)(k3)(2)
If t >= dt Then
dj = dict(k1)(k3)(0): ds = dict(k1)(k3)(1)
m = mrr(x - 1) + Int(mrr(x - 1) / dj) * ds '原本金+新收益后的本金
If t - dt >= min_t Then '能够线路递增,修改3个数组,跳出do循环
b(x) = k3: mrr(x) = m: trr(x) = t - dt: Exit Do
Else
If m >= res(2, 1) Then '仅终点变化,写入输出数组
b(x) = k3: mrr(x) = m: trr(x) = t - dt: s = ""
For j = 1 To x '路线结果
s = s & "-" & b(j)
Next
If m > res(2, 1) Then
r = 2: res(r, 1) = m - mm: res(r, 2) = trr(x): res(r, 3) = Mid(s, 2)
ElseIf m = res(2, 1) Then
r = r + 1: res(r, 1) = m - mm: res(r, 2) = trr(x): res(r, 3) = Mid(s, 2)
End If
End If
End If
End If
Next
change = True '同次路线最后一个终点迭代完成,下一步迭代之前的起点
Loop Until change = True
Do While change = True '最后一个起点迭代,循环进位
b(x) = "": mrr(x) = "": trr(x) = "": x = x - 1 '清除最后1个,进位
If x = 1 Then '第1个起点
k2 = b(x): dk = dict.Keys: change = False
If k2 <> dk(UBound(dk)) Then '不是最后1个
y = Application.Match(k2, dk, 0): b(x) = dk(y)
Else
b(x) = ""
End If
Else
k1 = b(x - 1): k2 = b(x): dk = dict(k1).Keys: y = Application.Match(k2, dk, 0)
For i = y To UBound(dk) 'y从1开始计数,=y即为y的下一个
k3 = dk(i): dt = dict(k1)(k3)(2)
If trr(x - 1) >= dt Then
dj = dict(k1)(k3)(0): ds = dict(k1)(k3)(1): dt = dict(k1)(k3)(2)
mrr(x) = mrr(x - 1) + Int(mrr(x - 1) / dj) * ds: trr(x) = trr(x - 1) - dt
b(x) = k3: Exit Do '跳出本次迭代
End If
Next
End If
Loop
Loop Until Len(b(1)) = 0 '所有遍历完后,完成所有进位时b(1)为空值
[g1].Resize(r, 3) = res
Debug.Print "路线选择完成,用时" & Format(Timer - tm, "0.00") '耗时
End Sub
- 运行结果:最大收益为406115,代码运行耗时
14.77
秒