【问题标题】:Round Robin Tournament algorithm in C#C# 中的循环赛算法
【发布时间】:2009-08-18 10:27:53
【问题描述】:

我在实现这个round robin 小项目时遇到了一些麻烦。我尝试做的是生成游戏的预览日历

那我要输出;

第 1 天: 第 1 队对第 2 队; 第 3 队对第 4 队; 5 队对 6 队;

第 2 天 第 1 队对第 4 队; 第 6 队对第 3 队; 第 2 队与第 5 队;

直到锦标赛结束;

这是我目前得到的代码,但我无法让第一个团队在阵列的其余部分旋转时修复...:

static void Main(string[] args)
   {
        string[] ListTeam = new string[] {"Equipe1", "Equipe2", "Equipe3", "Equipe4", "Equipe5", "Equipe6"};
        IList<Match> ListMatch = new List<Match>();
        it NumberOfDays = (ListTeam.Count()-1);
        int y = 2;

        for (int i = 1; i <= NumberOfDays; i++)
        {
            Console.WriteLine("\nDay {0} : \n",i);
            Console.WriteLine(ListTeam[0].ToString() + " VS " + ListTeam[i].ToString());

            for (y =ListTeam.Count(); y>0 ; y--)
            {
                Console.WriteLine(ListTeam[y].ToString() + " VS " + ListTeam[y+1].ToString());
                y++;
            }

        }
    }

编辑:我找到了一个 code sample in java but 我无法翻译...

【问题讨论】:

  • 我找不到办法让一队进入,但仍然可以输出其他比赛
  • @John Nolan : 抱歉错别字和拼写...
  • 您链接到的 Java 实现非常模糊。此外,文本和变量是法语的事实并没有帮助(至少我)。

标签: c# algorithm round-robin


【解决方案1】:

使用模运算应该很容易做到这一点:

更新 2:(如承诺的正确算法)

public void ListMatches(List<string> ListTeam)
{
    if (ListTeam.Count % 2 != 0)
    {
        ListTeam.Add("Bye");
    }

    int numDays = (numTeams - 1);
    int halfSize = numTeams / 2;

    List<string> teams = new List<string>();

    teams.AddRange(ListTeam.Skip(halfSize).Take(halfSize));
    teams.AddRange(ListTeam.Skip(1).Take(halfSize -1).ToArray().Reverse());

    int teamsSize = teams.Count;

    for (int day = 0; day < numDays; day++)
    {
        Console.WriteLine("Day {0}", (day + 1));

        int teamIdx = day % teamsSize;

        Console.WriteLine("{0} vs {1}", teams[teamIdx], ListTeam[0]);

        for (int idx = 1; idx < halfSize; idx++)
        {               
            int firstTeam = (day + idx) % teamsSize;
            int secondTeam = (day  + teamsSize - idx) % teamsSize;
            Console.WriteLine("{0} vs {1}", teams[firstTeam], teams[secondTeam]);
        }
    }
}

这将打印每天的团队比赛。

让我快速解释一下算法是如何工作的:

我注意到,由于我们正在轮换除第一个团队之外的所有团队,如果我们将除第一个团队之外的所有团队放入一个数组中,那么我们应该使用基于day 并进行模运算以正确环绕。在实践中,我们会将该数组视为在两个方向上无限重复,并且我们会逐渐向右(或向左)滑动视图。

然而,有一个障碍,那就是我们必须以一种非常特殊的方式对团队进行排序,才能使其正常工作。否则,我们不会得到正确的旋转。正因为如此,我们也需要以一种非常特殊的方式阅读匹配的二队。

准备清单的正确方法如下:

  • 切勿将第一支球队(Team#1)列入名单。
  • 将团队列表的后半部分放在列表的最前面。
  • 取出列表的前半部分,将其反转并将它们放入列表中(但不是 Team#1)。

现在,读取列表的正确方法如下:

  • 对于每一天,将您正在查看的第一个索引增加1
  • 对于您在该位置看到的第一支球队,将该球队与 Team#1 匹配。
  • 对于列表中的下一个团队 ((day + idx) % numDays),我们通常会将其与被抵消一半团队数量减 1 的团队进行匹配(减 1,因为我们自己处理了第一场比赛)。但是,由于我们列表的后半部分是通过还原准备的,因此我们需要在已还原的列表的后半部分中匹配该偏移量。一个更简单的方法是观察 in this 相当于匹配相同的索引,但从列表的末尾开始。给定当前的day 偏移量,即(day + (numDays - idx)) % numDays

更新 3: 我不高兴我的解决方案涉及如此复杂的选择、匹配、反转数组元素。在我考虑了我的解决方案所涉及的内容后,我意识到我太执着于保持团队的既定顺序。但是,这不是一项要求,并且可以通过不关心初始订购来获得不同但同样有效的时间表。重要的是我在解释的第二部分中描述的选择算法。

因此您可以简化以下几行:

teams.AddRange(ListTeam.Skip(halfSize).Take(halfSize));
teams.AddRange(ListTeam.Skip(1).Take(halfSize -1).ToArray().Reverse());

到:

teams.AddRange(ListTeam); // Copy all the elements.
teams.RemoveAt(0); // To exclude the first team.

【讨论】:

  • 到目前为止一切顺利,但如果你尝试输出 Team 1 vs Team 2,你会得到一个例外
  • 不,我在第 2 天获得两次相同的比赛:Team4 vs Team 5 第 5 天:Team5 vs Team4 Team3 vs Team4 相同
  • 嘿,对不起,我在我的实现中看到了错误。会解决的。
  • 奇怪的是我找不到为什么我不能这样做......它真的让我很紧张
  • 是的。在两个循环中解决它的问题 比我最初想象的要难。我正在处理它,会回帖。承诺。 :)
【解决方案2】:

听起来您想安排round-robin tournament。 wp文章包含算法。

我什至看不到您尝试旋转阵列的位置。排列看起来像: 1 -> 2 -> 3 -> 4 ... -> n/2 - 1 -> n - 1 -> n - 2 -> n - 3 -> ... -> n /2 -> 1(并且 0 保持固定)。您可以在 2 个循环中执行此操作(顶行和底行)。

【讨论】:

  • 谢谢你的回答,但是有没有办法随机生成呢?
  • 在哪里?我只能找到方法
  • ?方法应该直接导致适当的算法。添加团队的随机标签是一个微不足道的添加,并满足您的要求。可能会注意到 wp 页面所说的“顺时针旋转”是一个环,因此,1 2 3 4,顺时针旋转一个是 4 1 2 3。
  • 您可以随机排列团队和日期。
  • 嗯,当我回答时,我不记得那里有指向算法的链接。
【解决方案3】:

我对计算双循环调度

的已回答代码块进行了一些改进
GameEntities db = new GameEntities();


private void btnTeamFixtures_Click(object sender, RoutedEventArgs e)
    {
        txtResults.Text = null;

        var allTeams = db.Team.Select(t => t.TeamName);

        int numDays = allTeams.Count() - 1;
        int halfsize = allTeams.Count() / 2;

        List<string> temp = new List<string>();
        List<string> teams = new List<string>();

        teams.AddRange(allTeams);
        temp.AddRange(allTeams);
        teams.RemoveAt(0);

        int teamSize = teams.Count;

        for (int day = 0; day < numDays * 2; day++)
        {
            //Calculate1stRound(day);
            if (day % 2 == 0)
            {
                txtResults.Text += String.Format("\n\nDay {0}\n", (day + 1));

                int teamIdx = day % teamSize;

                txtResults.Text += String.Format("{0} vs {1}\n", teams[teamIdx], temp[0]);

                for (int idx = 0; idx < halfsize; idx++)
                {
                    int firstTeam = (day + idx) % teamSize;
                    int secondTeam = ((day + teamSize) - idx) % teamSize;

                    if (firstTeam != secondTeam)
                    {
                        txtResults.Text += String.Format("{0} vs {1}\n", teams[firstTeam], teams[secondTeam]);
                    }
                }
            }

            //Calculate2ndRound(day);
            if (day % 2 != 0)
            {
                int teamIdx = day % teamSize;

                txtResults.Text += String.Format("\n\nDay {0}\n", (day + 1));

                txtResults.Text += String.Format("{0} vs {1}\n", temp[0], teams[teamIdx]);

                for (int idx = 0; idx < halfsize; idx++)
                {
                    int firstTeam = (day + idx) % teamSize;
                    int secondTeam = ((day + teamSize) - idx) % teamSize;

                    if (firstTeam != secondTeam)
                    {
                        txtResults.Text += String.Format("{0} vs {1}\n", teams[secondTeam], teams[firstTeam]);
                    }
                }
            }
        }
    }

如果您愿意,您可以创建 2 个方法并像我在 2 个注释行中所做的那样传递整数(Day)来分隔代码。

如果您有任何问题或建议,请随时回复。

【讨论】:

    【解决方案4】:

    这可能是一种复杂的方法,但这可以简化为图论问题。为每个团队创建一个图顶点,并在每个顶点之间创建一条边(因此它是一个完整的图)。那么对于算法:

    对于每一天 i = 1 .. n :

    • 选择任意两个直接连接的未标记顶点,并用 i 标记它们之间的边。标记两个顶点。
    • 重复直到标记所有顶点。
    • 输出标记的边(即 team1 与 team2、team3 与 team4 等)
    • 从图中删除标记的边并将所有顶点重置为未标记。

    【讨论】:

    • 有点麻烦,我开始了
    • 很公平。这有点理论而非实际的解决方案。
    【解决方案5】:

    根据@paracycle 的回答,我提供了一个更好的代码,并进行了以下更改:

    • 我的方法是基于泛型类型T
    • 我的代码没有改变原来的teams列表实例
    • 改进的代码质量
    public static IEnumerable<(int Day, T First, T Second)> ListMatches<T>(IList<T> teams)
    {
        var matches = new List<(int, T, T)>();
        if (teams == null || teams.Count < 2)
        {
            return matches;
        }
    
        var restTeams = new List<T>(teams.Skip(1));
        var teamsCount = teams.Count;
        if (teams.Count % 2 != 0)
        {
            restTeams.Add(default);
            teamsCount++;
        }
    
        for (var day = 0; day < teamsCount - 1; day++)
        {
            if (restTeams[day % restTeams.Count]?.Equals(default) == false)
            {
                matches.Add((day, teams[0], restTeams[day % restTeams.Count]));
            }
    
            for (var index = 1; index < teamsCount / 2; index++)
            {
                var firstTeam = restTeams[(day + index) % restTeams.Count];
                var secondTeam = restTeams[(day + restTeams.Count - index) % restTeams.Count];
                if (firstTeam?.Equals(default) == false && secondTeam?.Equals(default) == false)
                {
                    matches.Add((day, firstTeam, secondTeam));
                }
            }
        }
    
        return matches;
    }
    

    测试代码:

    foreach (var match in ListMatches(new List<string> { "T1", "T2", "T3", "T4", "T5", "T6" }))
    {
        Console.WriteLine($"{match.Day} => {match.First}-{match.Second}");
    }
    

    6 个团队的输出:

    0 => T1-T2
    0 => T3-T6
    0 => T4-T5
    1 => T1-T3
    1 => T4-T2
    1 => T5-T6
    2 => T1-T4
    2 => T5-T3
    2 => T6-T2
    3 => T1-T5
    3 => T6-T4
    3 => T2-T3
    4 => T1-T6
    4 => T2-T5
    4 => T3-T4
    

    5 个团队的输出:

    0 => T1-T2
    0 => T4-T5
    1 => T1-T3
    1 => T4-T2
    2 => T1-T4
    2 => T5-T3
    3 => T1-T5
    3 => T2-T3
    4 => T2-T5
    4 => T3-T4
    

    【讨论】:

      【解决方案6】:

      我已经在 github RoundRobinTournamentSchedule 上发布了我的实现 可以找到对应的nuget包,在自己的解决方案中使用

      干杯

      【讨论】:

        【解决方案7】:

        那么如何计算你想要的每一天的可能组合呢

        1. 在每对中对它们进行排序,即 人数最少的队伍总是第一 任何一对。
        2. 按每天的第一个对列出的配对进行排序 对。

        【讨论】:

        • (NumberOfTeams /2) 及之后 ???我没有明白你的意思,但假设我需要加入第一支队伍并旋转阵列,直到它回到第一个节点,但我找不到方法
        • 我想先加入第一队,然后旋转阵列(我错过的部分)直到它回到原点
        • 我想我想说的是,而不是尝试进行轮换首先使用单程算法保持 team1,而不是使用双程算法。
        • 好吧,我想我现在明白了,你实际上是在努力争取所有可能的团队配对。在这种情况下,paracycle 的回答 stackoverflow.com/questions/1293058/… 会为您提供所有可能的配对,您可以将它们分配给每一天。
        【解决方案8】:

        假设我们总是有偶数球队/球员(如果奇数,添加 BYE;在我的情况下,球队/球员按他们的评分排名,我们希望让头号种子球队/球员先与较弱的对手比赛),这里是我的实现。

        void CreateSchedule(int n)
        {
            int[] orig = new int[n];
            for(int i=0;i<n; i++){
                orig[i] = i + 1;
            }   
            IEnumerable<int> rev = orig.Reverse();
        
            int len = orig.Length;
            for (int j = 0; j < len - 1; j++)
            {
                List<int> tmp = new List<int>();
                tmp.Add(orig[0]);
                tmp.AddRange(rev.Take(j).Reverse());
                if (j < len && len > 1 + j) tmp.AddRange(orig.Skip(1).Take(len - 1 - j));
                PrintMe(tmp, j + 1);
            }
        }
        
        void PrintMe(IEnumerable<int> arr, int round)
        {
        
            Console.WriteLine("----------Round {0}----------------", round);
            int halfSize = arr.Count() / 2;
        
            IEnumerable<int> A = arr.Take(halfSize);
            IEnumerable<int> B = arr.Skip(halfSize).Take(halfSize).Reverse();
        
            var Result = A.Zip(B, (x, y) => $"{x} vs {y}");
            Console.WriteLin(Result);
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-01-08
          • 1970-01-01
          • 1970-01-01
          • 2016-05-12
          • 1970-01-01
          • 2020-06-30
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多