简体   繁体   English

数组算法的组合

[英]Combinations of an array algorithm

I would like to find the combinations of an array of size 5 that adds up to 15. What would be the best way to go about doing this. 我想找到一个大小为5的数组的组合,这些数组的总和为15。什么是最好的方法?

Suppose I had the array 假设我有数组

7 8 10 5 3

What would be the best way to find all numbers that add up to 15 in C++ 在C ++中查找所有加起来为15的数字的最佳方法是什么

"the best" way depends on what you're optimizing. “最佳”方式取决于您正在优化的内容。

If there are not many elements in the array, there's an easy combinatoric algorithm: for all lengths from 1 to n (where n is the number of elements in the array), check all possible sets of n numbers and print each which sums to fifteen. 如果数组中没有太多元素,则有一个简单的组合算法:对于从1到n所有长度(其中n是数组中元素的数量),检查所有可能的n数字集,并打印每个总数为15的数字。

That would likely be the best from a time-to-implement standpoint. 从实现时间的角度来看,这可能是最好的。 A dynamic-programming solution (this is a DP problem) would likely be the best from a runtime efficiency standpoint; 从运行时效率的角度来看,动态编程解决方案(这是一个DP问题)可能是最好的。 a DP solution here is O(N³) , where the combinatoric solution is much much more than that. 这里的DP解决方案是O(N³) ,其中的组合解决方案远不止于此。

The gist of the DP algorithm (I'm not writing the code) is to go through your array, and keep track of all the possible sums that can be made with the sub-array you've seen so far. DP算法的要点(我不是在编写代码)是遍历您的数组,并跟踪到目前为止您所看到的子数组可能产生的所有总和。 As you reach each new array element, go through all the partial sums you got before and add it to them (not removing the original partial sum). 当您到达每个新的数组元素时,请遍历之前获得的所有部分和并将其添加到它们中(而不是删除原始的部分和)。 Whenever something hits 15 or passes it, discard that sum from the set you're tracking (print it if it hits 15 exactly). 每当某物达到或超过15时,就从您正在跟踪的集合中丢弃该总和(如果正好达到15则将其打印出来)。

my suggestion is go for a recursion. 我的建议是递归。

keeping track of the baseindex and currentindex and try to accumulate values every recursion 跟踪baseindex和currentindex并尝试在每次递归中累积值

return the integer value of the currentindex when accumulated value is 15 else if currentindex reaches 5 and accumulated value is not 15 return 0 当累加值是15时返回currentindex的整数值,否则,如果currentindex达到5而累加值不是15则返回0

when return is 0 and baseindex is still less than 5 then add 1 to base index and reset the current index and accumulated value and start recursion again. 当return为0且baseindex仍小于5时,则将1添加到基本索引中,并重置当前索引和累加值,然后再次开始递归。

If, as you mention in your comment, 10 is the highest number in the problem (also the maximum number of elements). 如您在评论中所述,如果10是问题中的最高数字(也是元素的最大数目)。 Then a brute force (with clever bitmasking, see this tutorial ) will do: 然后将使用蛮力(具有巧妙的位屏蔽,请参阅本教程 ):

// N is the number of elements and arr is the array.
for (int i = 0; i < (1 << N); ++i) {
    int sum = 0;
    for (int j = 0; j < N; ++j) if (i & (1 << j)) sum += arr[j];
    if (sum == required_sum); // Do something with the subset represented by i.
}

This algorithm has complexity O(N * 2^N). 该算法的复杂度为O(N * 2 ^ N)。 Note, the code is correct as long as N < 32. Notice the number of subsets with a certain sum can be exponential (more than 2^(N/2)). 注意,只要N <32,代码就是正确的。请注意,具有一定总和的子集的数量可以是指数的(大于2 ^(N / 2))。 Example, {1, 1, 1, 1, .., 1} and sum = N/2. 例如,{1,1,1,1,..,1}和sum = N / 2。

If, however, N is large but N * required_sum is not very large (up to millions), one can use the following recurrence (with dynamic programming or memoization): 但是,如果N大但N * required_sum不是很大(最多数百万),则可以使用以下重复发生(通过动态编程或存储):

f(0, 0) = 1
f(0, n) = 0 where n > 0
f(k, n) = 0 where k < 0
f(k + 1, S) = f(k, S - arr[k]) + f(k, S) where k >= 0

where f(k, S) denotes the possibility of getting a sum S with a subset of elements 0..k. 其中f(k,S)表示获得元素S为0..k的子集的和S的可能性。 The dynamic programming table can be used to generate all the subsets. 动态编程表可用于生成所有子集。 The running time of generating the table is O(N * S) where S is the required sum. 生成表的运行时间为O(N * S),其中S是所需的总和。 The running time of generating the subsets from the table is proportional to the number of such subsets (which can be very large). 从表中生成子集的运行时间与此类子集的数量(可能非常大)成正比。

General notes about the problem: 有关该问题的一般说明:

The problem in general is NP-Complete . 通常,问题是NP-Complete Therefore, it has no known polynomial time algorithm. 因此,它没有已知的多项式时间算法。 It does have however a pseudo-polynomial time algorithm , namely the recurrence above. 但是它确实有一个伪多项式时间算法 ,即上面的递归。

Sort the array of the elements. 对元素数组进行排序。 maintain two pointers, one on the beginning of the sorted array, and the other on the end of it. 维护两个指针,一个指针位于已排序数组的开头,另一个指针位于数组的结尾。 if the sum of the two elements is greater than 15, decrease the 2nd pointer. 如果两个元素的总和大于15,则减少第二个指针。 if the sum is less than 15, increase the 1st pointer. 如果总和小于15,则增加第一个指针。 if sum is equal to 15, record the two elements, and increase the 1st pointer. 如果sum等于15,则记录两个元素,并增加第一个指针。

Hope it works. 希望它能工作。

Recursion is one option I can think of. 递归是我能想到的一种选择。 Because I had some spare time on my hands I threw together this function (although it's probably unnecessarily large, and unoptimised to the extreme). 因为我有一些空闲时间,所以我将这个功能组合在一起(尽管它可能不必要地大,并且没有达到最佳状态)。 I only tested it with the numbers you provided. 我只用您提供的号码进行了测试。

void getCombinations( std::vector<int>& _list, std::vector<std::vector<int>>& _output, 
                      std::vector<int>& _cSumList = std::vector<int>(), int _sum = 0 )
{
    for ( std::vector<int>::iterator _it = _list.begin(); _it < _list.end(); ++_it)
    {
        _sum += *_it;
        _cSumList.push_back( *_it );
        std::vector<int> _newList;
        for ( std::vector<int>::iterator _itn = _list.begin(); _itn < _list.end(); ++_itn )
            if ( *_itn != *_it )
                _newList.push_back( *_itn );
        if ( _sum < 15 )
            getCombinations( _newList, _output, _cSumList, _sum );
        else if ( _sum == 15 )
        {
            bool _t = false;
            for ( std::vector<std::vector<int>>::iterator _itCOutputList = _output.begin(); _itCOutputList < _output.end(); ++_itCOutputList )
            {
                unsigned _count = 0;
                for ( std::vector<int>::iterator _ita = _itCOutputList->begin(); _ita < _itCOutputList->end(); ++_ita )
                    for ( std::vector<int>::iterator _itb = _cSumList.begin(); _itb < _cSumList.end(); ++_itb )
                        if ( *_itb == *_ita )
                            ++_count;
                if ( _count == _cSumList.size() )
                    _t = true;
            }
            if ( _t == false )
                _output.push_back( _cSumList );
        }
        _cSumList.pop_back();
        _sum -= *_it;
    }
}

Example usage with your numbers: 您的电话号码用法示例:

int _tmain(int argc, _TCHAR* argv[])
{
    std::vector<int> list;
    list.push_back( 7 );
    list.push_back( 8 );
    list.push_back( 10 );
    list.push_back( 5 );
    list.push_back( 3 );
    std::vector<std::vector<int>> output;
    getCombinations( list, output );
    for ( std::vector<std::vector<int>>::iterator _it = output.begin(); _it < output.end(); ++_it)
    {
        for ( std::vector<int>::iterator _it2 = (*_it).begin(); _it2 < (*_it).end(); ++_it2)
            std::cout << *(_it2) << ",";
        std::cout << "\n";
    }
    std::cin.get();
    return 0;
}

Best way is subjective. 最好的方法是主观的。 As I said, the code above could be improved tremendously, but should give you a starting point. 就像我说的那样,上面的代码可以进行很大的改进,但是应该为您提供一个起点。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM