将项目分组为3的算法

时间:2016-10-22 15:09:24

标签: algorithm combinatorics

我正在尝试解决一个问题,我有像这样的对:

A C
B F
A D
D C
F E
E B
A B
B C
E D
F D

我需要将它们组成3组,我必须从该列表中获得匹配的三角形。基本上我需要一个结果,如果它可能或不可以分组。

因此,可能的群组是(ACDBFE),或(ABCDEF),此群集可以分组,因为所有字母都可以分组3,没有人被遗漏。

我创建了一个脚本,我可以通过这些脚本来实现这一点,但是对于大型安装,它会变得太慢。

我的逻辑是:

make nested loop to find first match (looping untill I find a match)
 > remove 3 elements from the collection
  > run again

我这样做直到我没有信件。由于可以有不同的组合,我会从不同的字母开始多次运行,直到找到匹配为止。

我可以理解,这给了我至少N^N的循环,并且可能变得太慢。这些问题有更好的逻辑吗?可以在这里使用二叉树吗?

3 个答案:

答案 0 :(得分:6)

此问题可以建模为图Clique cover problem。每个字母都是一个节点,每一对都是边缘,您希望将图形划分为大小为3(三角形)的顶点不相交派系。如果您希望分区具有最小基数,那么您需要最小集团封面
实际上这将是一个k-clique封面问题,因为在clique封面问题中你可以拥有任意/不同大小的派系。

答案 1 :(得分:2)

正如Alberto Rivelli所说,这个问题可以简化为Clique Cover problem,这是NP难的。 它也可以减少找到特定/最大尺寸的集团的问题。也许还有其他人,而不是你的特殊情况可以减少的NP难题,但我没有想到任何问题。

然而,存在的算法可以在多项式时间内找到解,尽管并非总是在最坏的情况下。其中一个是Bron–Kerbosch algorithm,它是迄今为止最有效的查找最大团的算法,并且可以在最坏的情况下找到一个团(O ^(3 ^(n / 3))。我不知道你输入的大小,但我希望它足以解决你的问题。

这是Python中的代码,准备好了:

#!/usr/bin/python3
# @by DeFazer
# Solution to:
#  stackoverflow.com/questions/40193648/algorithm-to-group-items-in-groups-of-3
# Input:
#  N P - number of vertices and number of pairs
#  P pairs, 1 pair per line
# Output:
#  "YES" and groups themselves if grouping is possible, and "NO" otherwise
# Input example:
#  6 10
#  1 3
#  2 6
#  1 4
#  4 3
#  6 5
#  5 2
#  1 2
#  2 3
#  5 4
#  6 4
# Output example:
#  YES
#  1-2-3
#  4-5-6
# Output commentary:
#  There are 2 possible coverages: 1-2-3*4-5-6 and 2-5-6*1-3-4.
# If required, it can be easily modified to return all possible groupings rather than just one.

# Algorithm:
#  1) List *all* existing triangles (1-2-3, 1-3-4, 2-5-6...)
#  2) Build a graph where vertices represent triangles and edges connect these triangles with no common... vertices. Sorry for ambiguity. :)
#  3) Use [this](en.wikipedia.org/wiki/Bron–Kerbosch_algorithm) algorithm (slightly modified) to find a clique of size N/3.
#       The grouping is possible if such clique exists.

N, P = map(int, input().split())
assert (N%3 == 0) and (N>0)
cliquelength = N//3

pairs = {} # {a:{b, d, c}, b:{a, c, f}, c:{a, b}...}

# Get input

# [(0, 1), (1, 3), (3, 2)...]
##pairlist = list(map(lambda ab: tuple(map(lambda a: int(a)-1, ab)), (input().split() for pair in range(P))))
pairlist=[]
for pair in range(P):
    a, b = map(int, input().split())
    if a>b:
        b, a = a, b
    a, b = a-1, b-1
    pairlist.append((a, b))
pairlist.sort()

for pair in pairlist:
    a, b = pair

    if a not in pairs:
        pairs[a] = set()
    pairs[a].add(b)

# Make list of triangles
triangles = []
for a in range(N-2):
    for b in pairs.get(a, []):
        for c in pairs.get(b, []):
            if c in pairs[a]:
                triangles.append((a, b, c))
                break

def no_mutual_elements(sortedtupleA, sortedtupleB):
    # Utility function
    # TODO: if too slow, can be improved to O(n) since tuples are sorted. However, there are only 9 comparsions in case of triangles.
    return all((a not in sortedtupleB) for a in sortedtupleA)

# Make a graph out of that list
tgraph = [] # if a<b and (b in tgraph[a]), then triangles[a] has no common elements with triangles[b]
T = len(triangles)
for t1 in range(T):
    s = set()
    for t2 in range(t1+1, T):
        if no_mutual_elements(triangles[t1], triangles[t2]):
            s.add(t2)
    tgraph.append(s)

def connected(a, b):
    if a > b:
        b, a = a, b
    return (b in tgraph[a])

# Finally, the magic algorithm!

CSUB = set()
def extend(CAND:set, NOT:set) -> bool:
    # while CAND is not empty and there is no vertex in NOT connected to *all* vertexes in CAND
    while CAND and all((any(not connected(n, c) for c in CAND)) for n in NOT):
        v = CAND.pop()
        CSUB.add(v)
        newCAND = {c for c in CAND if connected(c, v)}
        newNOT = {n for n in NOT if connected(n, v)}
        if (not newCAND) and (not newNOT) and (len(CSUB)==cliquelength): # the last condition is the algorithm modification
            return True
        elif extend(newCAND, newNOT):
            return True
        else:
            CSUB.remove(v)
            NOT.add(v)

if extend(set(range(T)), set()):
    print("YES")
    # If the clique itself is not needed, it's enough to remove the following 2 lines
    for a, b, c in [triangles[c] for c in CSUB]:
        print("{}-{}-{}".format(a+1, b+1, c+1))
else:
    print("NO")

如果此解决方案仍然太慢,则可能更有效地解决Clique Cover问题。如果是这种情况,我可以尝试为它找到合适的算法。

希望有所帮助!

答案 2 :(得分:1)

我已经在JS中实现了我最自信的工作。我还尝试了从26个字母中随机选择的100000条边。如果它们都是唯一的而不是诸如["A",A"]这样的点,则它会在90~500毫秒内解析。最复杂的部分是获得不相同的群体,那些不仅仅是三角形变化的群体。对于给定的边缘数据,它在1毫秒内解析。

作为总结,第一个reduce阶段找到三角形,第二个reduce阶段将断开连接的阶段分组。

&#13;
&#13;
function getDisconnectedTriangles(edges){
 return edges.reduce(function(p,e,i,a){
                       var ce = a.slice(i+1)
                                 .filter(f => f.some(n => e.includes(n))), // connected edges
                           re = [];                                        // resulting edges
                       if (ce.length > 1){
                         re = ce.reduce(function(r,v,j,b){
                                          var xv = v.find(n => e.indexOf(n) === -1),  // find the external vertex
                                              xe = b.slice(j+1)                       // find the external edges
                                                    .filter(f => f.indexOf(xv) !== -1 );
                                          return xe.length ? (r.push([...new Set(e.concat(v,xe[0]))]),r) : r;
                                        },[]);
                       }
                       return re.length ? p.concat(re) : p;
                     },[])
             .reduce((s,t,i,a) => t.used ? s
                                         : (s.push(a.map((_,j) => a[(i+j)%a.length])
                                                    .reduce((p,c,k) => k-1 ? p.every(t => t.every(n => c.every(v => n !== v))) ? (c.used = true, p.push(c),p) : p
                                                                           : [p].every(t => t.every(n => c.every(v => n !== v))) ? (c.used = true, [p,c]) : [p])),s)
                                  ,[]);
}

var edges = [["A","C"],["B","F"],["A","D"],["D","C"],["F","E"],["E","B"],["A","B"],["B","C"],["E","D"],["F","D"]],
       ps = 0,
       pe = 0,
   result = [];

ps = performance.now();
result = getDisconnectedTriangles(edges);
pe = performance.now();
console.log("Disconnected triangles are calculated in",pe-ps, "msecs and the result is:");
console.log(result);
&#13;
&#13;
&#13;

您可以生成不同长度的随机边缘,并使用代码here

进行播放