Erlang 版 0-1 背包算法

0

Comments

因为以后可能要用到,于是今天用 Erlang 写了一个 0-1 背包算法,这算是我第一次用 Erlang 写算法,也是我第二次写 Erlang 程序……

以前都使用正常语言写,各种算法自然不会难写。所谓正常的语言,呃……也就是我们平时用的最多的语言了,像 C++、Python 什么的在我看来都还算正常的语言。那么 Erlang 到底哪里不正常了呢?

其实 Erlang 只要两个不正常的地方就足够囧死人了……那就是:

  1. 没有循环,必须用递归代替
  2. 数组不能随机访问,只能从头部读写

其实说数组不能随机访问吧,应该也是能的,只不过时间恐怕就不是 O(1) 了罢了。

首先我们回忆一下一维 0-1 背包算法是怎么写的?

在这里就不分析这个算法了,不知道 0-1 背包算法的,呃……算导里竟然没有……那就自己 Google 好了……

先贴一个随手写的伪代码:

1
2
3
4
for i = 1 to n
    for j = size - s[i] to 0
        if b[j] + v[i] > b[j+s[i]]
            b[j+s[i]] = b[j] + v[i]

唉……要是 Erlang 能像这个伪码这么简单就好了……

不过考虑到 Erlang 的特性,最后没有使用伪码这种形式,主要的改动有两点,一是把连续的大小数组改成了可能达大的大小的列表,二是使用双列表最后归并。至于记录路径什么的,都是小事……

最后贴代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
-module(knapsack).
-export([calc/2]).
 
calc(Objs, Size) ->
    K = [{0, 0, []}],
    calc(Objs, K, Size).
 
calc([], K, _) -> final(K);
calc([Obj|Objs], K, Size) ->
    NewK = calc(Obj, K, Size, []),
    FinalK = merge(lists:reverse(K), NewK),
    calc(Objs, FinalK, Size).
 
final(K) -> lists:reverse(final(K, {0, []})).
final([], {_, NameList}) -> NameList;
final([{_, Val, List}|K], {MaxVal, _} = Cur) ->
    if
        Val > MaxVal -> final(K, {Val, List});
        true -> final(K, Cur)
    end.
 
calc(_, [], _, NewK) -> NewK;
calc({_, ObjSize, _}, [{CurSize, _, _}|_], Size, NewK)
        when CurSize + ObjSize > Size ->
    calc(nothing, [], 0, NewK);
calc({ObjName, ObjSize, ObjVal} = Obj,
        [{CurSize, CurVal, CurList}|K], Size, NewK) ->
    calc(Obj, K, Size,
        [{CurSize + ObjSize, CurVal + ObjVal, [ObjName|CurList]}|NewK]).
 
merge(A, B) -> merge(A, B, []).
merge([], [], K) -> K;
merge(A, [], K) -> lists:reverse(A, K);
merge([], B, K) -> lists:reverse(B, K);
merge([{SizeA, ValA, ListA} = TA|LeftA] = A,
      [{SizeB, ValB, ListB} = TB|LeftB] = B, K) ->
    if
        SizeA > SizeB -> merge(LeftA, B, [TA|K]);
        SizeA < SizeB -> merge(A, LeftB, [TB|K]);
        ValA > ValB -> merge(LeftA, LeftB, [TA|K]);
        ValA < ValB -> merge(LeftA, LeftB, [TB|K]);
        length(ListA) < length(ListB) ->
            merge(LeftA, LeftB, [TA|K]);
        true ->
            merge(LeftA, LeftB, [TB|K])
    end.

用法解释:执行 knapsack:calc(物件列表, 背包大小),其中物件列表中每个物件以元组 {物件名称, 物件大小, 物件价值} 表示,最终返回物件名称的列表,价值相同情况下这个算法会返回包内物件平均价值最高的一种选法 (数量最少)。此外,这个算法保证输出列表中物件的顺序与输入顺序相同。

我知道代码风格很丑……而且还没有精心调试过……因为终于写出来太高兴了,所以就马上贴了,如果各位觉得不太对,请及时指出!谢谢……

Leave a Reply