我试图找出一种有效的算法来获取项目列表并生成所有唯一的子集,这些子集是将列表拆分为2个子列表的结果。我确信有一个通用的方法来做到这一点,但我对一个特定的案例感兴趣。我的列表将被排序,并且可能存在重复的项目。
一些例子:
输入
{1,2,3}
输出
{{1},{2,3}}
{{2},{1,3}}
{{3},{1,2}}
输入
{1,2,3,4}
输出
{{1},{2,3,4}}
{{2},{1,3,4}}
{{3},{1,2,4}}
{{4},{1,2,3}}
{{1,2},{3,4}}
{{1,3},{2,4}}
{{1,4},{2,3}}
输入
{1,2,2,3}
输出
{{1},{2,2,3}}
{{2},{1,2,3}}
{{3},{1,2,2}}
{{1,2},{2,3}}
{{1,3},{2,2}}
我可以在纸上做到这一点,但我正在努力找出一种以编程方式完成它的简单方法。我只是在寻找一个如何执行此操作的快速伪代码描述,而不是任何特定的代码示例。
感谢任何帮助。感谢。
答案 0 :(得分:2)
如果要生成所有子集,则最终会为长度为 n 的列表生成2个 n 子集。一种常见的方法是迭代所有数字 i 从0到2 n -1并使用设置的位 i 确定哪些项目在 i 子集中。这是有效的,因为任何项目在任何特定子集中都存在或不存在,因此通过迭代 n 位的所有组合,您将遍历2 n < / sup>子集。
例如,要生成(1,2,3)的子集,您将遍历数字0到7:
0 = 000 b →()
1 = 001 b →(1)
2 = 010 b →(2)
3 = 011 b →(1,2)
4 = 100 b →(3)
5 = 101 b →(1,3)
6 = 110 b →(2,3)
7 = 111 b →(1,2,3)
在您的问题中,您可以生成每个子集及其补码,以获得您的互斥子集对。当你这样做时,每一对都会重复,所以你只需要迭代2 n -1 - 1然后停止。
1 = 001 b →(1)+(2,3)
2 = 010 b →(2)+(1,3)
3 = 011 b →(1,2)+(3)
要处理重复项,您可以生成列表索引的子集而不是列表项的子集。与列表(1,2,2,3)一样,生成列表的子集(0,1,2,3),然后使用这些数字作为(1,2,2,3)列表的索引。基本上添加一个间接级别。
这里有一些Python代码将它们放在一起。
#!/usr/bin/env python
def split_subsets(items):
subsets = set()
for n in xrange(1, 2 ** len(items) / 2):
# Use ith index if ith bit of n is set.
l_indices = [i for i in xrange(0, len(items)) if n & (1 << i) != 0]
# Use the indices NOT present in l_indices.
r_indices = [i for i in xrange(0, len(items)) if i not in l_indices]
# Get the items corresponding to the indices above.
l = tuple(items[i] for i in l_indices)
r = tuple(items[i] for i in r_indices)
# Swap l and r if they are reversed.
if (len(l), l) > (len(r), r):
l, r = r, l
subsets.add((l, r))
# Sort the subset pairs so the left items are in ascending order.
return sorted(subsets, key = lambda (l, r): (len(l), l))
for l, r in split_subsets([1, 2, 2, 3]):
print l, r
输出:
(1,) (2, 2, 3)
(2,) (1, 2, 3)
(3,) (1, 2, 2)
(1, 2) (2, 3)
(1, 3) (2, 2)
答案 1 :(得分:1)
一些Erlang代码,问题是当你有重复的元素时会产生重复,所以结果列表仍然需要被过滤......
do([E,F]) -> [{[E], [F]}];
do([H|T]) -> lists:flatten([{[H], T}] ++
[[{[H|L1],L2},{L1, [H|L2]}] || {L1,L2} <- all(T)]).
filtered(L) ->
lists:usort([case length(L1) < length(L2) of true -> {L1,L2};
false -> {L2,L1} end
|| {L1,L2} <- do(L)]).
在伪代码中,这意味着:
答案 2 :(得分:1)
以下C ++函数完全符合您的需要,但顺序与示例中的顺序不同:
// input contains all input number with duplicates allowed
void generate(std::vector<int> input) {
typedef std::map<int,int> Map;
std::map<int,int> mp;
for (size_t i = 0; i < input.size(); ++i) {
mp[input[i]]++;
}
std::vector<int> numbers;
std::vector<int> mult;
for (Map::iterator it = mp.begin(); it != mp.end(); ++it) {
numbers.push_back(it->first);
mult.push_back(it->second);
}
std::vector<int> cur(mult.size());
for (;;) {
size_t i = 0;
while (i < cur.size() && cur[i] == mult[i]) cur[i++] = 0;
if (i == cur.size()) break;
cur[i]++;
std::vector<int> list1, list2;
for (size_t i = 0; i < cur.size(); ++i) {
list1.insert(list1.end(), cur[i], numbers[i]);
list2.insert(list2.end(), mult[i] - cur[i], numbers[i]);
}
if (list1.size() == 0 || list2.size() == 0) continue;
if (list1 > list2) continue;
std::cout << "{{";
for (size_t i = 0; i < list1.size(); ++i) {
if (i > 0) std::cout << ",";
std::cout << list1[i];
}
std::cout << "},{";
for (size_t i = 0; i < list2.size(); ++i) {
if (i > 0) std::cout << ",";
std::cout << list2[i];
}
std::cout << "}\n";
}
}
答案 3 :(得分:0)
我的建议是......
首先,计算您拥有的每个值的数量,可能在哈希表中。然后计算要考虑的组合总数 - 计数的乘积。
迭代这些组合。
在每个组合中,复制循环计数(作为x),然后通过哈希表项启动内部循环。
对于每个哈希表项,使用(x模数)作为第一个列表中哈希表键的实例数。在重复内循环之前将x除以计数。
如果您担心组合数量可能会溢出整数类型,则问题是可以避免的。对每个项目使用一个数组(每个hashmap键一个)从零开始,并通过组合'count'将每个数组项作为一个数字处理(所以整个数组代表组合数),但每个'digit'都有一个数字不同的基数(相应的计数)。也就是说,要“递增”数组,首先递增项目0.如果它溢出(变得等于其计数),则将其设置为零并递增下一个数组项目。重复溢出检查,直到溢出继续超过数组末尾,结束。
我认为sergdev对第二个使用非常类似的方法,但是使用std :: map而不是哈希表(std :: unordered_map应该可以工作)。对于大量项目,散列表应该更快,但不会以任何特定顺序为您提供值。通过哈希表中的键的每个循环的顺序应该是一致的,但除非你添加/删除键。