削减成本算法优化

时间:2017-10-28 08:19:28

标签: python algorithm python-3.x

我有一张木板,并在木板上给了N标记。现在我必须切割木板上的所有标记,以便将所有标记的切割成本降至最低。现在假设我先切割第i个标记然后通过使用两个乘数a和b作为输入给出成本,成本是a *(左)+ b *(右),其中左和右是切割后木材剩余部分的大小。例如,如果我有一个长度为10,a = 3和b = 4的木材,如果我有ex:[1,3,5,7,10]的标记列表,所以我不能切割第一个和最后一个标记,因为它们是起点和终点木头所以假设如果我首先从标记7开始,那么切割成本将是3 *(7-1)+ 4 *(10-7)= 18 + 12 = 30现在我将拥有的木材是以标记开始的木材1到标记7,其他将是标记7到木材末端的标记,我将重复该过程,直到所有标记都被切割。

现在看完这个问题之后,我立即想到,为了以最低的成本砍伐木材我首先需要找到中点(切点的中位数),然后我应该切割木材并再次重复这个过程再次,直到木材没有切割点,但我在解决切割后获得的正确木材方面遇到问题

样品输入:用[1,3,5,9,16,22]切割的木材,当我们首先从中位数9开始时,最小成本= 163,那么我们将得到标记木材[1,3,5, 9]和[9,16,22]现在首先解决左边的木材[1,3,5] [5,9],现在再次切割我们有[1,3] [3,5] [5, [9,16,22]现在正在操作这种木材,我们已经切割了所有的标记,列表将是[1,3] [3,5] [5,9] [9,16] ] [16,22]并且此操作的成本最低

这是我的代码:

#parent {
  display:grid;
  grid-template-columns: repeat(3, 1fr);
  grid-tempalte-rows: 1fr;
  grid-column-gap: 10px;
}

#parent > * {
  display: grid;
}

2 个答案:

答案 0 :(得分:3)

首先,这是一个递归生成器solve_gen,它检查所有可能的切割序列,然后选择最小切割序列。虽然代码是紧凑的,并且如果标记的数量很小,它运行正常,但随着标记数量的增加,它很快就会变得非常低效。我还添加了一个函数apply_cuts,它将一系列剪切应用于标记序列,因此您可以按顺序查看剪切。

solve_gen使用全局count来跟踪所做的递归调用的数量。 count对于算法的操作不是必需的,但是它给出了函数正在做多少工作的指示。

def cost(seq, m):
    return (seq[-1] - seq[0]) * m

def solve_gen(seq):
    global count
    count += 1
    if len(seq) == 2:
        yield 0, ()
        return
    for i in range(1, len(seq)-1):
        left, x, right = seq[:i+1], seq[i], seq[i:]
        val = cost(left, a) + cost(right, b)
        for lval, lcuts in solve_gen(left):
            for rval, rcuts in solve_gen(right):
                yield (val + lval + rval, (x,) + lcuts + rcuts)

def apply_cuts(seq, cuts):
    total = 0
    old = [seq]
    for x in cuts:
        new = []
        for u in old:
            if x in u:
                i = u.index(x)
                left, right = u[:i+1], u[i:]
                val = cost(left, a) + cost(right, b)
                new.extend((left, right))
            else:
                new.append(u)
        print(x, new, val)
        total += val
        old = new[:]
    return total

# Test

# Recursion counter
count = 0

a, b = 3, 4
seq = (1, 3, 5, 9, 16, 22)
print(seq)

results = list(solve_gen(seq))
val, cuts = min(results)
print('Cost: {}, Cuts: {}'.format(val, cuts))
print('Results length: {}, Count: {}'.format(len(results), count))

print('\nCutting sequence')
print(apply_cuts(seq, cuts))

<强>输出

(1, 3, 5, 9, 16, 22)
Cost: 163, Cuts: (9, 5, 3, 16)
Results length: 14, Count: 90

Cutting sequence
9 [(1, 3, 5, 9), (9, 16, 22)] 76
5 [(1, 3, 5), (5, 9), (9, 16, 22)] 28
3 [(1, 3), (3, 5), (5, 9), (9, 16, 22)] 14
16 [(1, 3), (3, 5), (5, 9), (9, 16), (16, 22)] 45
163

FWIW,以下是相同a&amp;的结果。 b标记序列较长。

(1, 3, 5, 9, 16, 22, 31, 33, 35, 39, 46)
Cost: 497, Cuts: (31, 16, 9, 5, 3, 22, 39, 35, 33)
Results length: 4862, Count: 41990

我们可以通过在递归的每个阶段找到最小值来提高效率,而不是找到所有可能性的最小值。然而,由于该算法研究了各种切割选项,因此它经常重复之前完成的计算。因此,我们可以通过使用缓存使代码更高效,即,我们将先前的结果存储在字典中,因此如果我们需要再次进行相同的剪切,我们可以在缓存中查找它而不是重新计算它。

我们可以编写自己的缓存,但functools模块提供了lru_cache,它可以用作装饰器。我们也可以给cost函数一个缓存,虽然它的计算非常简单,因此缓存可能不会节省很多时间。 lru_cache的一个很好的功能是它还可以提供缓存统计信息,这可以让我们知道缓存的有用性。

from functools import lru_cache

@lru_cache(None)
def cost(seq, m):
    return (seq[-1] - seq[0]) * m

@lru_cache(None)
def solve(seq):
    global count
    count += 1
    if len(seq) == 2:
        return 0, ()
    results = []
    for i in range(1, len(seq)-1):
        left, x, right = seq[:i+1], seq[i], seq[i:]
        val = cost(left, a) + cost(right, b)
        lval, lcuts = solve(left)
        rval, rcuts = solve(right)
        results.append((val + lval + rval, (x,) + lcuts + rcuts))
    return min(results)

# Test

# Recursion counter
count = 0

a, b = 3, 4
seq = (1, 3, 5, 9, 16, 22)
print(seq)

val, cuts = solve(seq)
print('Cost: {}, Cuts: {}'.format(val, cuts))
print('Count: {}\n'.format(count))

print('cost cache', cost.cache_info())
print('solve cache', solve.cache_info())

<强>输出

(1, 3, 5, 9, 16, 22)
Cost: 163, Cuts: (9, 5, 3, 16)
Count: 15

cost cache CacheInfo(hits=20, misses=20, maxsize=None, currsize=20)
solve cache CacheInfo(hits=26, misses=15, maxsize=None, currsize=15)

幸运的是,我们得到的结果与以前相同。 ;)请注意,递归计数现在要低得多。让我们用更长的标记序列来尝试。

(1, 3, 5, 9, 16, 22, 31, 33, 35, 39, 46)
Cost: 497, Cuts: (31, 16, 9, 5, 3, 22, 39, 35, 33)
Count: 55

cost cache CacheInfo(hits=240, misses=90, maxsize=None, currsize=90)
solve cache CacheInfo(hits=276, misses=55, maxsize=None, currsize=55)

与之前的41990相比,递归计数仅为55;显着减少。我们可以看到缓存正在被广泛使用。

FWIW,所有可能的切割序列的数量由Catalan numbers给出,这通常会在组合问题中出现。

答案 1 :(得分:1)

这个问题需要函数和递归。你想要的是这样的:

function total_cost(a, b, marks):
    # Do something clever here

现在你的标记少于3,问题很简单。不需要削减,成本为0。

function total_cost(a, b, marks):
    if len(marks) < 3:
        return 0
    else
        # Do something clever here

如果你有两个以上的标记,在任何特定地方切割的成本是在那里切割的成本,加上切割其余部分的成本。因此,切割成本是最小或那些成本。这应该足以填补一些聪明的东西。

此解决方案将以很多削减速度缓慢运行。要解决这个问题,你应该查找“memoization”。