元素=[1,2,3,4,5,6,7,8,9] 互斥=[(1,4),(2,5),(1,5),(5,6),(7,8),(3,9),(2,8),(4,5)]
把元素组成 N 个组, 保证互斥元素不在同一个组里, 并且 N 最小
算法渣渣求解, 这种问题应该怎么做呢?
1
stebest 2019-10-14 13:20:13 +08:00
对于原始数组,查询互斥条件,先把里面的元素互斥的元素全踢到另一个新建数组。然后对新数组递归操作,生成另一个新数组,直到新数组长度为 0。
|
2
aijam 2019-10-14 13:31:44 +08:00
1. 所有元素组成一个 complete graph。
2. 每一对互斥的 pair 对应 graph 里面的一条 edge,一一删除。 3. 答案是:删除 edge 之后得到的 graph 的 connected component 个数。 |
3
arrow8899 2019-10-14 13:31:46 +08:00
BFS
|
5
wutiantong 2019-10-14 13:51:34 +08:00
@aijam
1. 把(a, b) 二元组当作节点,其中 a 不等于 b,总共 n*(n-1)/2 个节点。 2. 在(a, b) 与 (b, c) 之间连线。 3. 从图中删掉所有互斥组合对应的节点。 4. 答案是剩余图的 connected component 个数。 |
6
wutiantong 2019-10-14 13:52:34 +08:00
@wutiantong 好像还是不对呢。
|
7
wanwenx 2019-10-14 13:54:36 +08:00 via Android
秋招的时候遇到了这题,滴滴的笔试,背景是垃圾分类,然后垃圾互斥,问最少的垃圾车需要多少?结果不会,233333
|
8
aijam 2019-10-14 14:02:04 +08:00
|
10
wutiantong 2019-10-14 14:06:56 +08:00
你这个 one-pass 遍历得不到最少分组呀。
|
11
aijam 2019-10-14 14:09:15 +08:00
@wutiantong 其实我也这么觉得,只是看看 greedy 分组啥效果 :D
|
12
arrow8899 2019-10-14 14:10:34 +08:00 3
# 先把每个元素的互斥元素找出来
1 (4, 5) 2 (5, 8) 3 (9) 4 (1, 5) 5 (1, 2, 4, 6) 6 (5) 7 (8) 8 (2, 7) 9 (3) # 新建一个空数组,检测数组中是否存在互斥的元素,不存在就放进去 [1] # 1 直接放进去 [1, 2] # 2 和 1 不互斥,放进去 [1, 2, 3] # 3 之和 9 互斥,放进去 [1, 2, 3] [4] # 4 和 1 互斥,新建一个数组 [1, 2, 3] [4] [5] # 5 和 1,4 都互斥,再新建数组 [1, 2, 3, 6, 7] [4, 8, 9] [5] # 然后 6789 同理 answer = 3 # 如果元素是连续的话,可以直接通过数组下标判断是否互斥,会快很多 |
13
ofooo OP @wanwenx 我要写个小工具其中需要解决这个问题, 没想到真的是个算法题啊.....
我的解法是 先循环每个元素 a, 把全部元素分成 2 组, A 组=包含元素 a 的最多的元素, B 组是其他元素 然后其他元素递归调用这个函数, 这样就获得的以 a 元素为核心的组, 找到数量最少的成组, 返回 然后加了一个临时变量, 保存每一组计算过的元素和最佳成组, 减少计算量 不知道这样算不算解出来了..... |
14
wutiantong 2019-10-14 14:13:54 +08:00
@wutiantong 我觉得我这个思路还可以抢救一下:
0. 首先我们要扩展补充 (complete) 所有(显式或隐含定义的)互斥二元组。 0-1. 比如说,假如给定(1, 2), (2, 3) 为互斥关系,那么(2, 3) 也是一个(隐含的)互斥关系。 0-2 从图的角度来说,任何两个元素只要有路径(path)连接,就应确保它们之间有一条直接的边(edge)。 1. 把(a, b) 二元组当作节点,其中 a 不等于 b,总共 n*(n-1)/2 个节点。 2. 在(a, b) 与 (b, c) 之间连线。 3. 从图中删掉所有(显式或隐含定义的)互斥二元组对应的节点。 4. 答案是剩余图的 connected component 个数。 |
15
wutiantong 2019-10-14 14:16:40 +08:00
@aijam 看我上一条的图解法,我觉得好像没问题了。
|
16
ofooo OP ```python3
class Test: def get_groups(self, items, reject_pairs): """ 生成由 items 组成的 N 个组, 保证每个组里的元素不互相排斥, 且 N 最小 :param items: [item1, item2, item3, ...] 要成组的全部元素 :param reject_pairs: {(item1, item3), (item2, item9) } 二元组集合, 每个二元组表示一个排斥条件 :return: [ [item1], [item2, item3], [...] ] 二维数组 """ self.sub_groups = {} # {排序的 item 元组: 最佳成组二维数组} 保存临时结果, 减小计算量 groups = self._get_groups(items, reject_pairs) return groups def _get_groups(self, items, reject_pairs): if len(items) == 1: return [[items[0]]] sort_types = tuple(sorted(items)) if sort_types in self.sub_groups: return self.sub_groups[sort_types] best_groups = None for t1 in items: now_group = [t1] # 可以和 t1 成组的元素组成的组 others = [] # 不能和 t1 成组的元素组成的组 for t2 in items: if t1 == t2: continue for tt in now_group: if self.is_cross(tt, t2, reject_pairs): others.append(t2) break else: now_group.append(t2) if len(others) == 0: best_groups = [now_group] self.sub_groups[sort_types] = best_groups return best_groups else: groups = self._get_groups(others, reject_pairs) + [now_group] if best_groups is None or len(best_groups) > len(groups): best_groups = groups self.sub_groups[sort_types] = best_groups return best_groups def is_cross(self, t1, t2, cross_pairs): if (t1, t2) in cross_pairs: return True if (t2, t1) in cross_pairs: return True return False ``` 我上面说的计算过程的代码...如果有问题大家多指出, 谢谢 |
17
ngc3242 2019-10-14 14:24:35 +08:00
感觉像是极大团问题,那就有可能是 NP 问题。元素数量不多的话倒是可以考虑状态压缩动态规划或者记忆化搜索。
|
18
necomancer 2019-10-14 14:29:26 +08:00
@arrow8899 这个正解
|
19
ofooo OP @wutiantong 这个图的解法挺巧妙的, 这样的算法复杂度大概是什么量级的呢?
|
20
wutiantong 2019-10-14 14:36:46 +08:00
@ofooo 多项式量级。
|
21
wutiantong 2019-10-14 14:42:07 +08:00
@ofooo 主体计算复杂度就是找出图的 conneted components。
在第 0 步中包含了一次这个计算,针对的是 N 个节点的图。 在最后一步中也包含了一次这个计算,针对的是 O(N*N) 个节点的图。 |
22
coordinate 2019-10-14 14:50:42 +08:00
通过矩阵表示图,初始化为 1,表示所有边都连接。然后遍历互斥条件,将图中不连接的边标记为 0。最后通过并查集确定多少个集合即可。
|
23
inu1255 2019-10-14 16:52:51 +08:00
@arrow8899
试试这个测试用例 元素=[1,2,3,4,5,6] 互斥=[(1,3),(2,4),(3,4)] 1 (3) 2 (4) 3 (1,4) 4 (2,3) # 新建一个空数组,检测数组中是否存在互斥的元素,不存在就放进去 [1] [1,2] [1,2],[3] [1,2],[3],[4] 按照这个算法得到 answer=3 而实际可以 [1,4], [2,3] answer=2 |
24
wutiantong 2019-10-14 17:01:01 +08:00
@inu1255
他这个算法额外引入了对元素顺序的依赖(从前向后遍历),仍然是一个 trivial 的贪心算法,不是最优解。 |
25
BlackBerry999 2019-10-14 17:04:21 +08:00
1.将元素作为 key,互斥条件作为值。如(“1”:“4,5”)
2.向数组内插入元素,且要插入的元素必须经过现有数组内元素的互斥条件判定。 3.循环执行 2,直至待插入元素都有分配的数组。 |
26
BlackBerry999 2019-10-14 17:06:58 +08:00
@BlackBerry999 步骤 1 的作用是生成一个元素的互斥条件判断列表。步骤 2 再进行判断时会使用到 1 生成的列表。
|
27
arrow8899 2019-10-14 17:17:48 +08:00
@inu1255 又想了想,感觉可以把元素按互斥元素个数排序,先处理互斥元素比较多的,互斥元素越多,对最终结果影响越大。
针对你提出的用例(之前的用例仍然可以通过): # 排序 3 (1,4) 4 (2,3) 1 (3) 2 (4) # 插值 [3] [3] [4] [3] [4, 1] [3, 2] [4, 1] # 只是一个猜想,不知道是否正确。😁 |
28
soy 2019-10-14 17:59:37 +08:00 3
|
29
ijustdo 2019-10-14 18:15:18 +08:00
@arrow8899 正解 我第一反应也是这么干
可能油更好的方法 但是这个起码可行 a = [1,2,3,4,5,6,7,8,9] x = [(1,4),(2,5),(1,5),(5,6),(7,8),(3,9),(2,8),(4,5)] x_info = {} # 根据互斥信息 找到 每个元素的互斥集合 for i in x: for j in i: if j not in x_info: x_info[j] = set() x_info[j] = x_info[j] | (set(i) - set([j])) In [65]: x_info Out[65]: {1: {4, 5}, 4: {1, 5}, 2: {5, 8}, 5: {1, 2, 4, 6}, 6: {5}, 7: {8}, 8: {2, 7}, 3: {9}, 9: {3}} In [63]: groups = [] In [64]: while a: ...: x = a.pop() ...: rr = [] ...: rr.append(x) ...: for i in a: ...: if i not in x_info.get(x, set()): ...: rr.append(i) ...: a.remove(i) ...: groups.append(rr) ...: In [61]: groups Out[61]: [[9, 1, 4, 6, 8], [7, 2, 5], [3]] In [72]: len(groups) Out[72]: 3 |
30
zjh6 2019-10-14 18:18:48 +08:00
我不能回复了吗?
|
31
ijustdo 2019-10-14 18:29:02 +08:00
哈哈 好像我那个不对
|
32
alexfu 2019-10-14 18:43:02 +08:00
@wutiantong 互斥条件可以看成一个 adjacency list,也就是一个 graph 的边的一种表示形式,同时也是个 sparse matrix,可以把互斥记作 1,不互斥记作 0,这个 sparse graph 记作 A, 这样找出所有间接关系的步骤即为 A*A*...*A 直到第 N 次的结果等于前一次的结果。将此结果记作 A'。A’的 maximum connected subgraph number 即为答案
|
33
alexfu 2019-10-14 18:43:54 +08:00
@wutiantong 这个是代码比较好写。。python 调包很容易。。时间复杂度就不一定好了。。
|
34
alexfu 2019-10-14 18:49:28 +08:00
@wutiantong 啊漏了一步 A' = 1 - (A*A*...*A). 掉包的话直接 scipy.sparse 和 igraph.components 估计 10 行就够了。。
|
35
ijustdo 2019-10-14 20:06:27 +08:00
[code]
a = [1,2,3,4,5,6] x = [(1,3),(2,4),(3,4)] a = [1,2,3,4,5,6,7,8,9] x = [(1,4),(2,5),(1,5),(5,6),(7,8),(3,9),(2,8),(4,5)] x_info = {} # 根据互斥信息 找到 每个元素的互斥集合 for i in x: for j in i: if j not in x_info: x_info[j] = set() x_info[j] = x_info[j] | (set(i) - set([j])) groups = [] all_is = {}.fromkeys(a) while a: ci = a.pop() rr = [] rr.append(ci) b = a.copy() while b: i = b.pop() can_in = True for j in rr: if i in x_info.get(j, set()): can_in = False break if can_in: rr.append(i) a.remove(i) groups.append(rr) print(x_info) print(groups) [/code] |
36
ijustdo 2019-10-14 20:11:23 +08:00
a = [1,2,3,4,5,6]
x = [(1,3),(2,4),(3,4)] {1: {3}, 3: {1, 4}, 2: {4}, 4: {2, 3}} [[6, 5, 4, 1], [3, 2]] ---------------------------- a = [1,2,3,4,5,6,7,8,9] x = [(1,4),(2,5),(1,5),(5,6),(7,8),(3,9),(2,8),(4,5)] {1: {4, 5}, 4: {1, 5}, 2: {8, 5}, 5: {1, 2, 4, 6}, 6: {5}, 7: {8}, 8: {2, 7}, 3: {9}, 9: {3}} [[9, 8, 6, 4], [7, 5, 3], [2, 1]] 看结果没有错 图分割的方法 就。。。。 |
37
lrxiao 2019-10-15 05:00:50 +08:00
图染色 register allocation (
|