C++ 使用索引j之前的至少n项查找所有组合 算法欲望
我正在尝试编写一个算法来查找数组中所有C++ 使用索引j之前的至少n项查找所有组合 算法欲望,c++,algorithm,combinations,subset,C++,Algorithm,Combinations,Subset,我正在尝试编写一个算法来查找数组中所有k项的长度组合,但也必须在索引j之前使用n项,因为我们需要的(n,j)对就有很多 例子 使用索引2之前的至少一项(akarestrictions={{1,2,3,4})查找{1,2,4}中的所有两项组合。这将导致{{1,2}、{1,3}、{1,4}、{2,3}、{2,4} 从{1,2,3,4,5,6,7}中查找所有三个项目组合,使用索引2之前的至少一个项目和索引4之前的两个项目,也称限制={{1,2},{2,4} 进展 我已经能够找到从一堆集合中选择一个项目
k
项的长度组合,但也必须在索引j
之前使用n
项,因为我们需要的(n,j)
对就有很多
例子
restrictions={{1,2,3,4}
)查找{1,2,4}
中的所有两项组合。这将导致{{1,2}、{1,3}、{1,4}、{2,3}、{2,4}
{1,2,3,4,5,6,7}
中查找所有三个项目组合,使用索引2之前的至少一个项目和索引4之前的两个项目,也称限制={{1,2},{2,4}
void add_combinations_to(vector<int> prefix, vector<vector<int>>::iterator start,
vector<vector<int>>::iterator stop, vector<vector<int>>& ret) {
if(start == stop) {ret.push_back(prefix); return;}
for(auto v : *start) {
auto next = prefix;
next.push_back(v);
add_combinations_to(next, start + 1, stop, ret);
}
};
int main() {
vector<vector<int>> v{{1,2},{3,4}};
vector<vector<int>> ret;
vector<int> init{};
add_combinations_to(init, v.begin(), v.end(), ret);
// ret now contains {{1,3},{1,4},{2,3},{2,4}}
}
void添加组合到(向量前缀,向量::迭代器开始,
向量::迭代器停止、向量和ret){
if(start==stop){ret.push_back(前缀);return;}
用于(自动v:*启动){
自动下一步=前缀;
下一步,向后推(v);
将组合添加到(下一步,开始+1,停止,返回);
}
};
int main(){
向量v{{1,2},{3,4};
向量ret;
向量init{};
将组合添加到(init,v.begin(),v.end(),ret);
//ret现在包含{1,3}、{1,4}、{2,3}、{2,4}
}
我觉得有一种方法可以扩展它。现在我需要退一步,但建议会很好。在大多数情况下,这只是一个问题,与许多序列枚举问题一样,这一问题符合标准词典枚举算法(引自:
- 从词典编纂的最小可能序列开始
- 虽然可能:
- a。向后扫描以找到可以增加的最后一个元素。(“可能是”意味着增加该元素仍然会导致某些有效序列的前缀。)
- b。将该元素增加到可能的下一个最大值
- c。用尽可能小的后缀填充序列的其余部分
j
sequencep==p0,p1,…,pj−1
可以是长度为k
的前缀,它是n
事物的组合k−j
。我们可以将其重写为pj−1
因此,对于无限制的k,n组合,以下简单函数将完成上述通用算法的步骤a和b:
/* Finds the (lexicographically) next prefix of a k-combination from n
* values, and returns the length of that prefix.
* If there is no possible next prefix, returns 0.
* Note: k is implicit in the length of the combination
*/
size_t nextPrefix(std::vector<size_t>& comb, size_t n) {
size_t k = comb.size();
for (size_t j = k; j;) {
--j;
if (comb[j] < n - k + j) {
++comb[j];
return j + 1;
}
}
return 0;
}
(这显然不是最优的。我们不必检查每个元素的所有限制,只需计算一个简单的最大值向量,然后根据该最大值测试元素。)
为了实际生成序列,我们需要能够填充后缀(通用算法的最后一步)并构造循环:
void firstSuffix(std::vector<size_t>& comb, size_t pfx_length) {
size_t val = pfx_length ? comb[pfx_length - 1] + 1 : 0;
for (auto it = comb.begin() + pfx_length; it != comb.end(); ++it)
*it = val++;
}
int main() {
std::vector<std::pair<size_t, size_t>> restrictions = {{1, 2}, {2, 4}, {3, 7}};
size_t k = std::max_element(restrictions.begin(), restrictions.end())->first;
if (k == 0) return 0; /* Empty set */
std::vector<size_t> comb(k);
std::size_t pfx = 0;
do {
firstSuffix(comb, pfx);
for (auto const& val : comb) std::cout << std::setw(3) << val;
std::cout << '\n';
} while (pfx = nextPrefix(comb, restrictions));
return 0;
}
void firstSuffix(标准::向量和梳,大小\u t pfx\u长度){
尺寸=pfx长度?梳[pfx长度-1]+1:0;
用于(自动it=comb.begin()+pfx_length;it!=comb.end();+it)
*it=val++;
}
然后我们可以写循环:
void firstSuffix(std::vector<size_t>& comb, size_t pfx_length) {
size_t val = pfx_length ? comb[pfx_length - 1] + 1 : 0;
for (auto it = comb.begin() + pfx_length; it != comb.end(); ++it)
*it = val++;
}
int main() {
std::vector<std::pair<size_t, size_t>> restrictions = {{1, 2}, {2, 4}, {3, 7}};
size_t k = std::max_element(restrictions.begin(), restrictions.end())->first;
if (k == 0) return 0; /* Empty set */
std::vector<size_t> comb(k);
std::size_t pfx = 0;
do {
firstSuffix(comb, pfx);
for (auto const& val : comb) std::cout << std::setw(3) << val;
std::cout << '\n';
} while (pfx = nextPrefix(comb, restrictions));
return 0;
}
intmain(){
向量限制={1,2},{2,4},{3,7};
size\t k=std::max\u元素(restrictions.begin(),restrictions.end())->first;
如果(k==0)返回0;/*空集*/
std::向量梳(k);
标准:尺寸\u t pfx=0;
做{
第一个后缀(梳、pfx);
对于(auto const&val:comb)std::cout,似乎可以简化限制。对于您的示例{1,2,3,4,5,6,7},我将使用std::next_permutation
。似乎可以简化限制。对于您的示例数据集={1,2,3,4,5,6,7},限制={1,2},{2,4}。限制可以是{1,2},(2-1),4},因为{1,2}假设前2中至少使用了一项,所以对于{2,4}只需要使用一项。示例2的解决方案是{1,2,3},{1,2,4},{1,3,4},{2,3,4},非常棒!感谢您的深入回答和所有解释。它有助于了解导致一个好的解决方案的背景主题。当然,示例代码很棒。