排序锦标赛种子

时间:2011-04-24 14:11:33

标签: javascript sorting

我正在制作一个HTML / JS驱动的单/双消除支架Web应用程序。我正在努力弄清楚如何从种子队/球员名单中分配第一轮比赛。例如,在8名球员的支架中,第一轮比赛是:

1V8 4V5 2V7 3V6

在更通用的术语中,种子可以被认为是一个数组(因为我通过弹出一个数组来指定团队匹配): 1,2,3,4,5,6,7,8-

需要分类到: 1,8,4,5,2,7,3,6

为了澄清,较高的种子需要在排序的数组中具有它们之间的最大距离,这使得在没有扰乱的括号中,较低的种子首先被淘汰并且与高种子的匹配尽可能晚地发生。实际上,想想一个网球锦标赛,你想要阻止16或32等支架的前4名球员互相比赛直到半决赛。因此, 16 种子支架的正确数组输出为:

1,16,8,9,4,13,5,12,2,15,7,10,3,14,6,11

转换为以下第一轮比赛:

1v16 8v9 4v13 5v12 2v15 7v10 3v14 6v11

感谢Matt Ball提供8种子支架的正确算法

6 个答案:

答案 0 :(得分:8)

从顶部和底部匹配球员的想法是正确的但不完全。这样做一次对第一轮很有用:

while (seeds.length)
{
    firstRound.push(seeds.shift());
    firstRound.push(seeds.pop());
}
1, 2, 3, 4, 5, 6, 7, 8 => 1, 8, 2, 7, 3, 6, 4, 5

...但是在第二轮中,种子1遇到种子2和3遇到4.我们需要为每轮进行第一次/最后一次洗牌。第一次,我们将每个元素单独移动。第二次,我们移动每个 PAIR 元素。我们第三次移动四个小组等,直到我们的小组大小为seeds.length/2。像这样:

// this is ruby, aka javascript psuedo-code :)

bracket_list = seeds.clone

slice = 1
while slice < bracket_list.length/2
  temp = bracket_list
  bracket_list = []

  while temp.length > 0
    bracket_list.concat temp.slice!(0, slice)       # n from the beginning
    bracket_list.concat temp.slice!(-slice, slice)  # n from the end
  end

  slice *= 2
end
return bracket_list

这是在进行迭代时数组的样子(括号表示增加的组大小):

1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16

(1, 16),  (2, 15),  (3, 14),  (4, 13),   (5, 12),   (6, 11),   (7, 10),   (8, 9)

(1, 16, 8, 9),  (2, 15, 7, 10),  (3, 14, 6, 11),  (4, 13, 5, 12)

(1, 16, 8, 9, 4, 13, 5, 12),  (2, 15, 7, 10, 3, 14, 6, 11)

所以现在,在排名最后的8名球员之后,我们留下了1, 8, 4, 5, 2, 7, 3, 6。在底部4被淘汰后,我们有1, 4, 2, 3,而在最后一轮只有1, 2

如果不能画出一个括号,很难解释这一点......如果我能为你澄清一些内容,请告诉我。

答案 1 :(得分:2)

这可能不如使用自定义sort函数的 @ alex的回答那样有效,但肯定更容易编写和理解:

// This algorithm assumes that seeds.length is an even number
var seeds = [1, 2, 3, 4, 5, 6, 7, 8],
    firstRound = [];

while (seeds.length)
{
    firstRound.push(seeds.shift());
    firstRound.push(seeds.pop());
}

// seeds is now empty
// firstRound is now [1, 8, 2, 7, 3, 6, 4, 5]

Demo 1


实际上,我只想到了一个更快的算法(就地“排序”,需要O(n)次):

// Also assumes that seeds.length is an even number
var seeds = [1, 2, 3, 4, 5, 6, 7, 8],
    numSeeds = seeds.length,
    stop = numSeeds >> 1,
    temp;

for (var i=1; i<stop; i=i+2)
{
    temp = seeds[i];
    seeds[i] = seeds[numSeeds-i];
    seeds[numSeeds-i] = temp;
}

// seeds is now [1, 8, 3, 6, 5, 4, 7, 2]

Demo 2

请注意,这些算法都不会像OP中那样生成完全相同的 order 对,但它们都会生成相同的 set 对:

  • (1,8)
  • (2,7)
  • (3,6)
  • (4,5)

答案 2 :(得分:2)

我提出了一个解决方案,但它不在“排序数组”的范围之内。

(javascript)代码位于http://jsbin.com/ukomo5/2/edit

在基本术语中,算法假设括号中不会出现扰乱,因此种子1和2 应该在最后一轮中相遇。它遍历每轮中的每个种子(从预先计算的总决赛开始,向后工作),计算当前种子(在迭代中)赢得的前一轮中的未知种子。这可以做到,因为给定种子和数字,你可以弄清楚其他种子应该是什么:

其他种子=圆形+ 1中的种子数 - 已知种子

为了说明,在半决赛中:

半决赛1(已知种子为1):其他种子= 4 + 1 - 1 = 4

半决赛2(已知种子为2):其他种子= 4 + 1 - 2 = 3

当我看到我画的“无心烦”支架时,我才注意到这种模式。

在最后的迭代(即第1轮)中,所有种子及其位置都是已知的,准备分配给匹配。正确的排序数组如下:

1,16,8,9,4,13,5,12,2,15,7,10,3,14,6,11

再次感谢Matt Ball提出了一个小括号的正确解决方案(如果没有详细的上下文,很难说明问题和所需的解决方案,我在最初的问题中没有做到这一点)。

如果有人有其他解决方案或更优雅的解决方案,请告诉我们!

答案 3 :(得分:1)

这是我开发的算法。第1步是绘制一个包含尽可能多的行的表(四舍五入为2的幂),并根据需要绘制尽可能多的列来表示二进制中的团队数量。说,有8支球队。该表最初看起来像这样(点表示水平单元格边框):

。 。 。 | | | |  。 。 。 | | | |  。 。 。 | | | |  。 。 。 | | | |  。 。 。 | | | |  。 。 。 | | | |  。 。 。 | | | |  。 。 。 | | | |  。 。

列从左侧按升序编号。对于每列,每隔2 ^(列号)行放一个星号。也就是说,第1列中的每第2行,第2列中的每第4行等等。

。 。 。 | | | |  。 。 。 | | | |  *。 。 | | | |  。 。 。 | | | |  * *。 | | | |  。 。 。 | | | |  *。 。 | | | |  。 。 。 | | | |


从第1行的每列中的0开始。此后,对于每列中的连续行,从0-1和1-0切换,除非该行中有星号。这是结果:

。 。 。 | 0 | 0 | 0 |  。 。 。 | 1 | 1 | 1 |  *。 。 | 1 | 0 | 0 |  。 。 。 | 0 | 1 | 1 |  * *。 | 0 | 1 | 0 |  。 。 。 | 1 | 0 | 1 |  *。 。 | 1 | 1 | 0 |  。 。 。 | 0 | 0 | 1 |


最后一步是评估将0和1的字符串作为二进制数处理的每一行。这将导致0-7的值。每个添加1会产生1-8的值。这些对应于种子。

。 。 。 | 0 | 0 | 0 | + 1 = 1  。 。 。 | 1 | 1 | 1 | + 1 = 8  *。 。 | 1 | 0 | 0 | + 1 = 5  。 。 。 | 0 | 1 | 1 | + 1 = 4  * *。 | 0 | 1 | 0 | + 1 = 3  。 。 。 | 1 | 0 | 1 | + 1 = 6  *。 。 | 1 | 1 | 0 | + 1 = 7  。 。 。 | 0 | 0 | 1 | + 1 = 2


每对种子都是按顺序播放的比赛。即。 1-8,5-4,3-6和7-2。这可以扩展到任何数量的种子。当由于条目的数量小于2的幂而要插入字节时,它们采用最高的种子值。例如。如果只有28个条目,那么byes将分配给29,30,31和32的位置。

答案 4 :(得分:0)

我用PHP编写了一个解决方案(参见https://stackoverflow.com/a/45566890/760777)。 这是javascript版本。

它会将所有种子返回到正确的位置。匹配与他的示例相同,但在更漂亮的顺序中,种子1和种子编号8位于模式的外部(正如您在网球锦标赛中看到的那样)。

如果没有沮丧(意味着较高种子的球员总是从低种子球员中获胜),你将在决赛中以种子1和种子2结束。

它实际上做了两件事:

  1. 显示正确的顺序(这是将byes放在正确位置的要求)

  2. 它会在正确的位置填写(如果需要)

  3. 关于单个消除括号应该是什么样子的完美解释:http://blog.playdriven.com/2011/articles/the-not-so-simple-single-elimination-advantage-seeding/

    8名参与者的代码示例:

    var NUMBER_OF_PARTICIPANTS = 8; // Set the number of participants
    
    if (!String.prototype.format) {
      String.prototype.format = function() {
        var args = arguments;
        return this.replace(/{(\d+)}/g, function(match, number) { 
          return typeof args[number] != 'undefined' ? args[number] : match;
        });
      };
    }
    
    var participants = Array.from({length: NUMBER_OF_PARTICIPANTS}, (v, k) => k + 1) ;
    var bracket = getBracket(participants);
    
    console.log(bracket);
    
    function getBracket(participants)
    {
      var participantsCount = participants.length;	
      var rounds = Math.ceil(Math.log(participantsCount)/Math.log(2));
      var bracketSize = Math.pow(2, rounds);
      var requiredByes = bracketSize - participantsCount;
    	
      console.log("Number of participants: {0}".format(participantsCount));
      console.log("Number of rounds: {0}".format(rounds));
      console.log("Bracket size: {0}".format(bracketSize));
      console.log("Required number of byes: {0}".format(requiredByes));    
        
      if(participantsCount < 2) {
        return [];
      }
        
      var matches = [[1,2]];
      
      for(var round = 1; round < rounds; round++) {
        var roundMatches = [];
        var sum = Math.pow(2, round + 1) + 1;
        
        for(var i = 0; i < matches.length; i++) {
          var home = changeIntoBye(matches[i][0], participantsCount);
          var away = changeIntoBye(sum - matches[i][0], participantsCount);
          roundMatches.push([home, away]);
          home = changeIntoBye(sum - matches[i][1], participantsCount);
          away = changeIntoBye(matches[i][1], participantsCount);
          roundMatches.push([home, away]);
        }
        matches = roundMatches;   
        
      }   
      
      return matches;    
    }
    
    function changeIntoBye(seed, participantsCount)
    {
        //return seed <= participantsCount ?  seed : '{0} (= bye)'.format(seed);  
        return seed <= participantsCount ?  seed : null;
    }

    将NUMBER_OF_PARTICIPANTS从8更改为6以获得两个字节。

    祝你好运。 RWC

答案 5 :(得分:0)

Cliff的算法here给我留下了深刻的印象。我认为这非常聪明。这是我用ruby编写的一个简单实现。 BYE返回为-1。

def seed(n)
  rounded_n = next_power_of_two(n)
  nbr_bits_required=rounded_n.to_s(2).length-1
  binary_seeds = Array.new(rounded_n) {Array.new(nbr_bits_required)}
  binary_seeds[0]=Array.new(nbr_bits_required){0}

  nbr_bits_required.times do |col|    
    1.upto(rounded_n-1) do |row|
      if row % (2**(col+1)) == 0
        #asterisk in the previous row, don't inverse the bit
        binary_seeds[row][col] = binary_seeds[row-1][col]
      else
        #no asterisk in the previous row, inverse the bit
        binary_seeds[row][col] = binary_seeds[row-1][col] == 0 ? 1 : 0
      end
    end
  end

  #output the result in decimal format
  binary_seeds.collect {|bs| s=(bs.join("")).to_i(2)+1; s>n ? -1 : s}
end

def next_power_of_two(n)
  k = 1
  k*=2 while k<n
  k
end

测试一下:

seed(8)
=> [1, 8, 5, 4, 3, 6, 7, 2]

seed(6)
=> [1, -1, 5, 4, 3, 6, -1, 2]