我正在为以下问题寻找一种有效的算法:
有一个带有值的数组,即(注意有意省略索引0)
Index 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12
Value 17, 12, 5, 22, 3, 12, 6, 13, 7, 0, 2, 15
我需要找到的是这些约束下的指数子集:
例如,如果子集长度为3且总和为20,则所有可能的解决方案都是
Indices: [1, 7, 12] Sum of values: 17 + 6 + 15 = 38
Indices: [1, 8, 11] Sum of values: 17 + 13 + 2 = 32
Indices: [1, 9, 10] Sum of values: 17 + 7 + 0 = 24
Indices: [2, 6, 12] Sum of values: 12 + 12 + 15 = 39
Indices: [2, 7, 11] Sum of values: 12 + 6 + 2 = 20
Indices: [2, 8, 10] Sum of values: 12 + 13 + 0 = 25
Indices: [3, 5, 12] Sum of values: 5 + 3 + 15 = 23
Indices: [3, 6, 11] Sum of values: 5 + 12 + 2 = 19
Indices: [3, 7, 10] Sum of values: 5 + 6 + 0 = 11
Indices: [3, 8, 9] Sum of values: 5 + 13 + 7 = 25
Indices: [4, 5, 11] Sum of values: 22 + 3 + 2 = 27
Indices: [4, 6, 10] Sum of values: 22 + 12 + 0 = 34
Indices: [4, 7, 9] Sum of values: 22 + 6 + 7 = 35
Indices: [5, 6, 9] Sum of values: 3 + 12 + 7 = 22
Indices: [5, 7, 8] Sum of values: 3 + 6 + 13 = 22
其中[2, 6, 12]
是最佳解决方案,因为它具有最大值总和。
目前我使用略微修改的分区算法运行所有可能的组合,随着索引总和的增长呈指数级增长,所以我想知道是否有更好的方法?
答案 0 :(得分:1)
获取数组,然后按值而不是按索引对它们进行排序(保持索引值对保持不变)。现在,从数组的末尾开始,取下indices数组中的最后一个k数,其中k是你必须拥有的索引数,并将它们相加。如果它等于所需的总和,那么你就完成了。如果没有,请注意差异(所需的总和 - 实际总和),并将其添加到第(n-k)个索引。在索引数组中找到该索引(按值排序,请注意),现在找到新的值总和(您可以通过减去旧索引的值并添加新值来优化此值,而不是重新计算总和所有k值)。
您现在有一个有效的解决方案和一个下限。您知道有效解决方案的其余部分的索引甚至可能超过此分数,必须在值排序数组中的最小索引值之后。那就是:
两者按值排序 -
indices: | bunch of indices | index we found | more | k-1 'random' indices |
values: | bunch of values | value for ^ | more | k-1 largest values |
所以我们只需搜索更多'以及满足条件的有效索引的k-1最大值,并且还具有形成较大总和的值。为此,我们冲洗并重复,将最小的(n-k-1)元素向后移动一个,因此我们有效地尝试这些元素的所有组合,但是按照我们的k个元素集的子集和减少的顺序。这允许我们在找到更大的总和时不断缩小我们搜索的空间,因为确定任何包含比最佳解的值更小的值的总和将具有更小的总和(因为该集合的其余部分已经如此大可能的)。
伪代码:
pair_array = input() // each pair consists of index and value
sort_by_value(pair_array)
best_sum = 0
markers = [n - (k-1) .. n] // mark the k-1 indices being summed
while True:
sum_of_indices = sum_indices(pair_array[markers])
value_sum = sum_value(pair_array[markers])
if pair_array.contains(desired_sum - sum_of_indices): // this lets us effectively reduce our search by a factor of N, given contains uses a hashtable
value_sum += pair_array(pair_array.index(desired_sum - sum_of_indices)).value
if value_sum > best_sum:
best_sum = value_sum
pair_array.remove(0 .. n - (k-1)) // this greatly reduces the combinations checked
if has_next_combination(markers, pair_array):
next_greatest_combination(markers, pair_array) // pick new markers, in a reverse-binary counting fashion (most significant bit first way)
else:
print(best_sum)
break
答案 1 :(得分:1)
解决方案O(I.S.K)
我们先做一些命名:
I
是最大的索引(在您的示例中为12)S
是选择索引的值的总和(在您的示例中为20)K
是所选索引的数量V[]
链接到索引的值数组maxsum(s, i, k)
使用k
索引可达到的最大总和,所有差异,其值小于或等于i
且其总和为s
。然后你想找到maxsum(S, I, K)
您的问题表现出一些不错的特性:
例如,在尝试计算maxsum(s, i, k)
时,我可以不使用索引i
,在这种情况下,值为maxsum(s, i-1, k)
。或者我可以使用索引i
。在这种情况下,我想解决子问题:使用i-1
这样的索引,索引小于或等于s-i
且其总和为k-1
的最大总和是多少。这是值:V[i] + maxsum(s-i, i-1, k-1)
。
由于我们希望达到最大金额,我们最终得到:(修改:更正maxsum(s-i, i-1, k)
至maxsum(s-i, i-1, k-1)
)
maxsum(s, i, k) = max{ maxsum(s, i-1, k) ; V[i] + maxsum(s-i, i-1, k-1) }
这是dynamic programming解决的典型问题。
这是一个示例C ++程序,用于解决O(I.S.K)
(空间和时间)中的问题。
我们可以将空间复杂度提高到O(I.S)
,代价是时间复杂度更高:O(I.S.K²)
。
如何使用该程序
g++ -std=c++14 -g -Wall -O0 dp.cpp -o dp
./dp input.txt
其中 input.txt 是具有以下格式的文件:
运行示例
---- K=1 ----
17 12 5 22 3 12 6 13 7 0 2 15
[ 1][ 2][ 3][ 4][ 5][ 6][ 7][ 8][ 9][10][11][12]
[ 1] 17 17 17 17 17 17 17 17 17 17 17 17
[ 2] 12 12 12 12 12 12 12 12 12 12 12
[ 3] 5 5 5 5 5 5 5 5 5 5
[ 4] 22 22 22 22 22 22 22 22 22
[ 5] 3 3 3 3 3 3 3 3
[ 6] 12 12 12 12 12 12 12
[ 7] 6 6 6 6 6 6
[ 8] 13 13 13 13 13
[ 9] 7 7 7 7
[10] 0 0 0
[11] 2 2
[12] 15
[13]
[14]
[15]
[16]
[17]
[18]
[19]
[20]
---- K=2 ----
17 12 5 22 3 12 6 13 7 0 2 15
[ 1][ 2][ 3][ 4][ 5][ 6][ 7][ 8][ 9][10][11][12]
[ 1]
[ 2] 12 12 12 12 12 12 12 12 12 12 12
[ 3] 29 29 29 29 29 29 29 29 29 29 29
[ 4] 22 22 22 22 22 22 22 22 22 22
[ 5] 17 39 39 39 39 39 39 39 39 39
[ 6] 34 34 34 34 34 34 34 34 34
[ 7] 27 27 29 29 29 29 29 29 29
[ 8] 8 24 24 24 24 24 24 24
[ 9] 25 25 25 30 30 30 30 30
[10] 34 34 34 34 34 34 34
[11] 15 28 28 28 28 28 28
[12] 9 35 35 35 35 35
[13] 18 18 29 29 29 32
[14] 25 25 25 25 27
[15] 19 19 19 24 24
[16] 13 13 13 37
[17] 20 20 20 20
[18] 13 13 27
[19] 7 15 21
[20] 9 28
---- K=3 ----
17 12 5 22 3 12 6 13 7 0 2 15
[ 1][ 2][ 3][ 4][ 5][ 6][ 7][ 8][ 9][10][11][12]
[ 1]
[ 2]
[ 3]
[ 4]
[ 5] 17 17 17 17 17 17 17 17 17 17
[ 6] 34 34 34 34 34 34 34 34 34 34
[ 7] 51 51 51 51 51 51 51 51 51
[ 8] 44 44 44 44 44 44 44 44 44
[ 9] 39 39 41 41 41 41 41 41 41
[10] 42 42 42 42 42 42 42 42
[11] 37 51 51 51 51 51 51 51
[12] 30 46 46 46 46 46 46 46
[13] 39 40 52 52 52 52 52
[14] 20 35 47 47 47 47 47
[15] 37 37 42 42 42 42 44
[16] 31 37 37 37 41 41
[17] 40 40 40 40 40 54
[18] 21 47 47 47 47 49
[19] 41 41 41 41 44
[20] 22 35 35 35 39
index: 12 sum: 20
index: 6 sum: 8
index: 2 sum: 2
max sum: 39
源代码
#include <cstdio>
#include <iomanip>
#include <iostream>
#include <limits>
#include <valarray>
#include <vector>
using namespace std;
auto const INF = numeric_limits<double>::infinity();
struct matrix {
matrix(size_t rows, size_t cols, double value)
: cells(value, rows*cols)
, rows(rows)
, cols(cols)
, value(value)
{}
double& operator() (int r, int c)
{
if(r < 0 || c < 0)
return value;
return cells[r*cols+c];
}
valarray<double> cells;
size_t rows;
size_t cols;
double value;
};
int main(int argc, char* argv[]) {
if(argc > 1)
freopen(argv[1], "r", stdin);
// I: max index
// S: sum of indices
// K: number of indices in the sum S
int I, S, K;
cin >> I >> S >> K;
// load values
vector<double> V(I+1, 0);
for(int i=1; i<=I; ++i)
cin >> V[i];
// dynamic programming:
// --------------------
// maxsum(i, s, k) is the maximal sum reachable using 'k' indices, less
// than or equal to 'i', all differents, and having a sum of 's'
//
// maxsum(i, s, k) =
// -oo if i > s
//
// -oo if i < s && k == 1
//
// V[s] if i >= s && s <= I && k == 1
// -oo if (i < s || s > I) && k == 1
//
// max { V[i] + maxsum(i-1, S-i, k-1), maxsum(i-1, S, k) }
vector<matrix> maxsum(K+1, matrix(S+1, I+1, -INF));
// initialize K=1
for(int s=0; s<=I && s<=S; ++s) {
for(int i=s; i<=I; ++i) {
maxsum[1](s, i) = V[s];
}
}
// K > 1
for(int k=2; k<=K; ++k) {
for(int s=2; s<=S; ++s) {
for(int i=1; i<=I; ++i) {
auto l = V[i] + maxsum[k-1](s-i, i-1);
auto r = maxsum[k](s, i-1);
maxsum[k](s, i) = max(l, r);
}
}
}
// display the whole dynamic programming tables (optional)
for(int k=1; k<=K; ++k) {
cout << "---- K=" << k << " ----\n";
cout << " ";
for(int i=1; i<=I; ++i) {
cout << setw(3) << V[i] << ' ';
}
cout << '\n';
cout << " ";
for(int i=1; i<=I; ++i) {
cout << '[' << setw(2) << i << ']';
}
cout << '\n';
for(int s=1; s<=S; ++s) {
cout << '[' << setw(2) << s << "] ";
for(int i=1; i<=I; ++i) {
if(maxsum[k](s, i) == -INF) {
cout << " ";
} else {
cout << setw(3) << maxsum[k](s, i) << ' ';
}
}
cout << '\n';
}
}
// output the indices belonging to the solution by working backward in the
// dynamic programming tables
int t_S = S;
int t_I = I;
for(int k=K; k>=1; --k) {
if(t_I <= 0 || t_S <= 0) {
cout << "error...\n";
break;
}
auto m = maxsum[k](t_S, t_I);
int i;
for(i=t_I; i>=1; --i) {
if(maxsum[k](t_S, i) != m)
break;
}
cout << "index: " << setw(3) << (i+1) << ' ';
cout << "sum: " << setw(3) << t_S << '\n';
t_I = i;
t_S = t_S - i - 1;
}
cout << "max sum: " << maxsum[K](S, I) << '\n';
}
答案 2 :(得分:0)
我能想到的一个小技巧,如果你试图找到3个索引,而不是迭代3个索引,你可以在知道前两个索引时计算第3个索引。例如,当你知道
时p1 = 1,p2 = 7 =&gt; p3 = 20 - (p1 + p2)= 12
当有N个索引时,这可以推广,最后一个索引总是可以从N-1个先前的索引中推断出来。
我在Python中试过这个:
Index = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ]
Value = [17, 12, 5, 22, 3, 12, 6, 13, 7, 0, 2, 15 ]
maxSum = 0 SumInd = 20
for p1 in range(1,len(Index)+1):
for p2 in range(p1,len(Index)+1):
if (p1 + p2 < SumInd) and (p1 != p2):
p3 = SumInd - (p1 + p2) #If you know p1 and p2 calculate p3 index
if (p3 < len(Index)+1) and (p2 != p3) and (p1 != p3):
fooSum = Value[p1-1]+Value[p2-1] + Value[p3-1]
print(p1,p2,p3 , "Sum is ",fooSum)
if maxSum < fooSum:
maxSum = fooSum
print("Max Sum is ", maxSum)
当然,您需要在找到maxSum时保存索引。此实现也计算了许多相似的对(例如[1,9,10]和[9,10,1])。或许更好的解决方案可以消除这些对。
编辑:大改进我找到了一种消除大多数不必要检查的方法。假设您需要3个索引。第一个检查整个范围的可能值。假设它是index1。
其他两个指数必须总和为20 - ind1让我们称之为休息。索引列表始终是有序的,因此您可以使用返回索引显示最小值(列表中的第一项大于index1)和显示最大值的前索引(列表中的最后一项)。所以index2 = backIndex,index3 = frontIndex。
如果剩余部分小于index2,3的总和,你可以增加后退索引(获得下一个更大的值),或者如果它更大,你减少前面的索引,直到两个索引相遇,你破坏和增加索引1。这消除了两次检查(1,7,12)和(1,12,7)。
代码在Python中:
maxSum = 0
SumInd = 20
for index_1 in range(1,len(Index)):
rest = SumInd - index_1
backIndex = index_1+1
frontIndex = len(Index)
while backIndex < frontIndex:
if rest > (backIndex + frontIndex):
backIndex = backIndex + 1
elif rest < (backIndex + frontIndex):
frontIndex = frontIndex - 1
else:
fooSum = Value[index_1-1]+Value[backIndex-1] + Value[frontIndex-1]
print("Checking for ",index_1,backIndex,frontIndex,' Sum of values:',fooSum)
if maxSum < fooSum:
indList = [index_1-1,backIndex,frontIndex]
maxSum = fooSum
backIndex = backIndex + 1 #To avoid Inf loop
print("Max Sum is ", maxSum,"at",indList)
并给出了这些结果:
Checking for 1 7 12 Sum of values: 38
Checking for 1 8 11 Sum of values: 32
Checking for 1 9 10 Sum of values: 24
Checking for 2 6 12 Sum of values: 39
Checking for 2 7 11 Sum of values: 20
Checking for 2 8 10 Sum of values: 25
Checking for 3 5 12 Sum of values: 23
Checking for 3 6 11 Sum of values: 19
Checking for 3 7 10 Sum of values: 11
Checking for 3 8 9 Sum of values: 25
Checking for 4 5 11 Sum of values: 27
Checking for 4 6 10 Sum of values: 34
Checking for 4 7 9 Sum of values: 35
Checking for 5 6 9 Sum of values: 22
Checking for 5 7 8 Sum of values: 22
Max Sum is 39 at [1, 6, 12]
对于N个索引,这总是可以推广。第一个N-2索引可以搜索列表的整个范围(如上面的情况中的索引1,还应该注意所有这些索引从先前的索引值开始检查加上一个直到列表的末尾以消除许多重复检查)。
最后两个索引可以像我在代码中显示的那样计算,并避免重复检查。