分组算法

4
我正在帮助某人编写一个程序,我认为这应该很容易,但事实上从来都不是这样 :)
我正在尝试将班级花名册(通常在10-20名学生之间)有效地分组,使每个同学都能与另一个同学配对,以形成独特的小组。因此,在10人的班级中,您可以有9个小组。
它需要能够处理奇数学生数量,这让我感到困惑。
我考虑用Java来实现,但这并不是必须的。有没有算法的方法能够保证a)不会无限循环(最终出现已经是伙伴关系的两个人),b)我希望时间效率更高而不是空间效率,因为班级规模很小!
谢谢!

你是想要分组一次,还是多次? - Landon
一个有10个人的班级如何有9对独特的组合? - Simon Lehmann
这听起来像是一道作业问题... - Steven A. Lowe
1
谁在乎这是不是一道作业题呢?这是一个关于算法的合法问题。至少它不是关于ASCII的“隐藏功能”... - Landon
@Simon:9组,即9个唯一的成对集合。 - Moishe Lettvin
6个回答

2
你想创建一个完全图,将每个学生作为节点,然后随机选择边并将它们插入到一个唯一的集合中。
在下一次“拉取”时,你想做同样的事情,但是现在,如果已经选择了该边,则放弃并重新选择。

这难道不允许10人班级中有超过9个小组吗? - Chris Johnson
总数?哦,是的。有10的阶乘种可能性(如果我记得我的图论方程式正确的话)。需要放置约束和错误检查,但我相信OP可以解决。 - Paul Nathan
总共有(n^2 - n)/2条边。由于你为每个“组”(即完全配对的类)选择了n/2条边,所以可能的组合数为(n^2 - n)/n -- 它的增长率是O(n),而不是O(n!)。 - Moishe Lettvin
抱歉,我的代数知识很差。(n^2 - n)/n 当然是 n-1。所以,是的:10人班级可以分成9组。 - Moishe Lettvin
1
这个不行。 考虑这种情况,给定集合{ABCDEF}。 首先,随机选择{(AB),(CD),(EF)}。 接下来,随机选择{(AC),(DB),...你挂了。EF已经被用完了,但第三对还没有被选择。 你需要更多的限制条件。 - Jude Allred

2
这里是解决问题的C#代码。
我假设你真的关心最大化学生配对的独特性,而不是可能独特的学生配对组合集合。
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;

namespace Pairing
{
    class Program
    {
        static void Main(string[] args)
        {
            //switch these lines if you'd prefer a command line interface to a text file.
            var rgs = File.ReadAllLines("Roster.txt");
            //var rgs = args;

            var setPairs = new HashSet<HashSet<string>>();
            for (var ixrgs = 0; ixrgs < rgs.Length - 1; ixrgs++)
                for (var ixrgsSubset = ixrgs + 1; ixrgsSubset < rgs.Length; ixrgsSubset++)
                    setPairs.Add(new HashSet<string>(new string[] { rgs[ixrgs], rgs[ixrgsSubset] }));

            var setGroups = new HashSet<HashSet<HashSet<string>>>();
            var setUsedPairs = new HashSet<HashSet<string>>();
            while (setPairs.Count > 0)
            {
                var setPairsTmp = new HashSet<HashSet<string>>(setPairs);
                var setTmp = new HashSet<HashSet<string>>();
                var setUsedVariables = new HashSet<string>();

                while (setPairsTmp.Count > 0)
                {
                    //give me the first element
                    var pair = setPairsTmp.First<HashSet<string>>();
                    //store it
                    setTmp.Add(pair);
                    //add it to our set of used variables
                    setUsedVariables.UnionWith(pair);
                    //remove it from our set of available pairs.
                    setPairsTmp.RemoveWhere(set => set.Intersect<string>    (setUsedVariables).Count<string>() != 0);

                    //remove its implicated deadlocks from our set of available pairs
                    //(this step is both gross, and key. Without it, failure potential arises.)
                        var s1 = new HashSet<string>();
                        var s2 = new HashSet<string>();
                        //get the set of variables paired with the first:
                        var rgPair = pair.ToArray<string>();
                        foreach (var set in setUsedPairs)
                        {
                            if (set.Contains(rgPair[0]))
                                s1.UnionWith(set);
                            if(set.Contains(rgPair[1]))
                                s2.UnionWith(set);
                        }
                        s1.IntersectWith(s2);
                        //enumerate the pairs created by the deadlocking pairs, remove them from our available selections.
                        var rgsTmp = s1.ToArray<string>();
                        for (var ixrgs = 0; ixrgs < rgsTmp.Length - 1; ixrgs++)
                            for (var ixrgsSubset = ixrgs + 1; ixrgsSubset < rgsTmp.Length; ixrgsSubset++)
                                setPairsTmp.RemoveWhere(set => set.Contains(rgsTmp[ixrgs]) && set.Contains(rgsTmp[ixrgsSubset]));
                }
                setPairs.ExceptWith(setTmp);
                setGroups.Add(setTmp);
                setUsedPairs.UnionWith(setTmp);
            }
            //at this point, setGroups contains the set of unique group combinations.
            //the non-maximally sized groups indicate unique sets that could form provided that
            //all other students are in redundant sets.

            var enumerableGroups = setGroups.OrderByDescending<HashSet<HashSet<string>>, int>(set => set.Count);
            //Sanity Check:
            foreach (var group in enumerableGroups)
            {
                Console.Write("{");
                foreach (var pair in group)
                    Console.Write(string.Format(@"[{0},{1}]", pair.ToArray<string>()));
                Console.WriteLine("}");
            }
        }
    }
}

1
这对我来说是一个不寻常的答案 - 建议“下载应用程序” - 但是这里是:

您所描述的可能与国际象棋锦标赛配对足够相似。

看看这个:http://home.swipnet.se/rullchef/chessp/

这里有一个莫纳德系统的解释,这可能是您想要的:

Monrad System

蒙拉德系统是杯赛系统的一个非常有趣的变体,据我所知,它只在国际象棋比赛中定期使用。在第一轮比赛中,所有队伍都会随机配对。胜者得1分,失败者得0分。在每个后续的回合中,所有得分相同的队伍将被随机配对(除非有其他配对可能性,否则之前已经相遇过的队伍不能再次配对)。这个系统的优点是,所有队伍都可以继续比赛,与杯赛系统不同的是,随着赛季(或锦标赛)的进行,实力相当的队伍将会相遇。没有轮数的限制,但如果有类似但不一定相同的积分,最终必须将队伍配对。在预定的一组回合后,得分最高的队伍将成为获胜者。


1
你所询问的算法似乎与循环赛程安排算法大致相同。详细信息可在该维基百科文章中找到。您还可以在网络上找到一些生成器来进行快速尝试,其中一个可以在这里找到。

1
这是Vlion上面答案的伪代码。这不是最快的方法,但它是概念的说明(感谢Vlion!)
// create all the edges
for i := 1 to number_of_students - 1
  for j := i + 1 to number_of_students
    edges.add(new Edge(i,j))

// select all groups from the edges
for x := 1 to number_of_students - 1
  used_nodes.clear
  group.clear

  for y := 1 to number_of_students div 2
    do
      current_edge = edges.get_next
    while (current_edge.n1 not in used_nodes) and
          (current_edge.n2 not in used_nodes)

    used_nodes.add(current_edge.n1)
    used_nodes.add(current_edge.n2)

    group.add(current_edge)

    edges.remove(current_edge)

  groups.add(group)

0

我不确定这是否完全符合您的要求,但这是我用简单的Python实现的方法。

它会输出您可以为10个学生创建的每个唯一分组。

我想它可能不是最快的方法,但它非常易于实现和跟踪。

from itertools import permutations

def my_sort(x):
    assert type(x) in (tuple, list)
    assert len(x)==10
    groups = x[0:2],x[2:4],x[4:6],x[6:8],x[8:10]
    groups = sorted([sorted(g) for g in groups], key=lambda k:k[0])
    return tuple(x  for g in groups for x in g )

S = set(my_sort(p) for p in permutations(list(range(10))))

"""
len(S) == 945
list(sorted(S))[-3:] == [(0, 9, 1, 8, 2, 7, 3, 4, 5, 6), (0, 9, 1, 8, 2, 7, 3, 5, 4, 6), (0, 9, 1, 8, 2, 7, 3, 6, 4, 5)]
"""

一个元组表示一行中的所有组: (0、9、1、8、2、7、3、4、5、6) 表示 0 与 9 分组,1 与 8 分组,依此类推。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接