Algorithm 排球运动员组合

Algorithm 排球运动员组合,algorithm,combinations,Algorithm,Combinations,一些背景: 在排球比赛中,运动员在池中比赛以确定排名。球队是成对的球员。比赛是一对球员对另一对球员的比赛。在这个例子中,我们假设只有一个场地可以进行比赛,当一名球员不在比赛时,他们会坐着/等待。一个池中的玩家数量将在4到7之间。如果一个池中有8名玩家,他们会将其分成2个4人的池 我想计算每个玩家与其他玩家进行比赛的最少比赛次数。 例如,一个四人游泳池将有以下队伍: import itertools players = [1,2,3,4] teams = [t for t in itertools

一些背景:
在排球比赛中,运动员在池中比赛以确定排名。球队是成对的球员。比赛是一对球员对另一对球员的比赛。在这个例子中,我们假设只有一个场地可以进行比赛,当一名球员不在比赛时,他们会坐着/等待。一个池中的玩家数量将在4到7之间。如果一个池中有8名玩家,他们会将其分成2个4人的池

我想计算每个玩家与其他玩家进行比赛的最少比赛次数。

例如,一个四人游泳池将有以下队伍:

import itertools
players = [1,2,3,4]
teams = [t for t in itertools.combinations(players,2)]
print 'teams:'
for t in teams:
    print t
产出:

teams:
(1, 2)
(1, 3)
(1, 4)
(2, 3)
(2, 4)
(3, 4)
((1, 2), (3, 4))
((1, 3), (2, 4))
((1, 4), (2, 3))
以及匹配的数量:

matches = []
for match in itertools.combinations(teams,2):
    # A player cannot be on both teams at the same time
    if set(match[0]) & set(match[1]) == set():
        matches.append(match)

for match in matches:
    print match
产出:

teams:
(1, 2)
(1, 3)
(1, 4)
(2, 3)
(2, 4)
(3, 4)
((1, 2), (3, 4))
((1, 3), (2, 4))
((1, 4), (2, 3))
这是正确的,但当我向池中添加第5名玩家时,此算法会中断:

((1, 2), (3, 4))
((1, 2), (3, 5))
((1, 2), (4, 5))
((1, 3), (2, 4))
((1, 3), (2, 5))
((1, 3), (4, 5))
((1, 4), (2, 3))
((1, 4), (2, 5))
((1, 4), (3, 5))
((1, 5), (2, 3))
((1, 5), (2, 4))
((1, 5), (3, 4))
((2, 3), (4, 5))
((2, 4), (3, 5))
((2, 5), (3, 4))
这些团队重复了很多次

我试着保留一份参赛球队的名单,但结果证明这个算法是贪婪的。我的意思是,当它到达(1,5)队时,所有其他队[(2,3),(2,4),(3,4)]都已经打过了,(1,5)再也没有打过了

我要找的是:

((1,2), (3,4)) (player 5 waits)
((1,3), (2,5)) (player 4 waits)
((1,4), (3,5)) (player 2 waits)
((1,5), (4,2)) (player 3 waits)
((2,3), (4,5)) (player 1 waits)
对于每个池大小,手工计算会更容易吗?还是可以在python中轻松完成

谢谢你的帮助


编辑:
删除了Python标记。任何语言都可以,我可以把它转换成Python

必须有更好的方法来做到这一点,但这里有一个开始:

import itertools
import operator
from copy import deepcopy as clone

def getPossibleOpponents(numPlayers):
    matches = list(itertools.combinations(itertools.combinations(range(1,numPlayers+1), 2), 2))
    possibleMatches = [match for match in matches if len(set(itertools.chain.from_iterable(match)))==4]
    answer, playedTeams = {}, set()
    opponents = {}
    for team, teams in itertools.groupby(possibleMatches, key=operator.itemgetter(0)):
        playedTeams.add(team)
        opponents[team] = [t for t in next(teams) if t!=team]

    return opponents

def updateOpponents(opponents, playedTeams):
    for team in playedTeams:
        if team in opponents:
            opponents.pop(team)
    for k,v in opponents.items():
        opponents[k] = [team for team in v if team not in playedTeams]

def teamSeatings(opponents, answer=None):
    if answer is None:
        answer = {}
    if not len(opponents):
        if not(len(answer)):
            return None
        print(answer)
            sys.exit(0)

    for k,v in opponents.items():
        if not v:
            return None

        newOpponents = clone(opponents)
        for away in opponents[k]:
            if k in newOpponents:
                newOpponents.pop(k)
            answer[k] = away
            updateOpponents(newOpponents, {itertools.chain.from_iterable(i[0] for i in answer.items())})
            teamSeatings(newOpponents, answer)

if __name__ == "__main__":
    opps = getPossibleOpponents(5)
    teamSeatings(opps)

我不懂Python,但我忍不住在Ruby中尝试它。希望这能很容易地转化为Python。如果你不认识Ruby,我很乐意解释这里发生了什么:

num_players = gets.to_i
players = (1..num_players).to_a
teams = players.combination(2).to_a

def shuffle_teams( teams, players )
  shuffled_teams = teams.shuffle
  x = 0
  while x < shuffled_teams.length
    if shuffled_teams[x] - shuffled_teams[x + 1] == shuffled_teams[x]
      x += 2
    else
      return shuffle_teams( teams, players )
    end
  end
  x = 0
  while x < shuffled_teams.length
    team_1 = shuffled_teams[x]
    team_2 = shuffled_teams[x + 1]
    waiting = players.select do |player|
      ![team_1, team_2].flatten.include?(player)
    end
    print "(#{team_1}, #{team_2}), waiting: #{waiting}\n"
    x += 2
  end
end

shuffle_teams( teams, players )
对于5名玩家:

([2, 4], [1, 3]), waiting: [5]
([1, 5], [3, 4]), waiting: [2]
([1, 4], [2, 5]), waiting: [3]
([3, 5], [1, 2]), waiting: [4]
([2, 3], [4, 5]), waiting: [1]
但是,它不适用于6或7名玩家,因为每一个玩家都会产生奇数个组合。这个问题在现实生活中是如何处理的?不知何故,一支球队必须打两场

编辑:此脚本现在将通过复制其中一个团队来处理6或7个玩家池。在Python中应该很容易复制,因为它只是依赖于对团队数组进行洗牌,直到他们适应了适当的顺序。起初,我觉得我用这种方法有点作弊,但鉴于Anonymous的解释,这是一个NP完全问题(假设我正确理解这意味着什么:-),这可能是解决小型池问题的最佳方法(根据您的系统,随机排序的池数超过9个左右,但幸运的是,这超出了我们的场景范围)。此外,随机排序具有非个人的优势,如果玩家因为必须玩两次而第二次没有得分而感到不安,这可能会很有用!下面是脚本:

num_players = gets.to_i
players = (1..num_players).to_a
teams = players.combination(2).to_a

def shuffle_teams( teams, players )
  shuffled_teams = teams.shuffle
  x = 0
  while x < shuffled_teams.length
    if !shuffled_teams[x + 1]
      shuffled_teams[x + 1] = shuffled_teams.find do |team|
        shuffled_teams[x] - team == shuffled_teams[x]
      end
    end
    if shuffled_teams[x] - shuffled_teams[x + 1] == shuffled_teams[x]
      x += 2
    else
      return shuffle_teams( teams, players )
    end   
  end
  x = 0
  while x < shuffled_teams.length
    team_1 = shuffled_teams[x]
    team_2 = shuffled_teams[x + 1]
    waiting = players.select do |player|
      ![team_1, team_2].flatten.include?(player)
    end
    print "(#{team_1}, #{team_2}), waiting: #{waiting}\n"
    x += 2
  end
end

shuffle_teams( teams, players )

在组合数学中,这被称为惠斯特循环。也许对漫画感兴趣的数学专家更倾向于玩惠斯特而不是沙滩排球?但这是另一回事

惠斯特循环赛 安排一个由两个人组成的双人比赛面对另一个双人团队的问题称为Whist循环赛-阅读更多内容并查找算法

在玩家数量为4n的情况下,这是最容易实现的。其他三个案例是使用幽灵玩家和幽灵团队构建的。应该面对幽灵团队的玩家只是坐在那一轮之外

基本的想法是一个玩家被锁定,比如说玩家一。其他玩家然后“旋转”,让玩家二在第一轮与玩家一合作,并与玩家二和三相遇。下一轮玩家一停留并与玩家三合作,他们面对玩家二和四。请看我从上面的链接借用的这个可视化

我已经实现了上面描述的算法来安排一个与海滩排球具有相似特征的torunament,它工作起来很有魅力


在python中实现这一点应该没有问题。如果有问题,请回答一些具体问题。祝您好运!:)

您可以将其表示为一个集合覆盖问题。对于4名玩家,选择所有(无序)玩家对:

PP := {{0,1}, {0,2}, {0,3}, {1,2}, {1,3}, {2,3}}
一个可能的匹配是这些对中无序的一对,这样双方的玩家就不一样了。此处,可能的匹配项为:

M := {{{0,1},{2,3}}, {{0,2},{1,3}}, {{0,3},{1,2}}}
现在的问题是,您希望找到该集合的最小子集,以便其并集是所有玩家对的集合,PP

这是一个NP完全的例子。也许将集合限制为成对会给出一个更简单的解决方案,但如果不是这样的话,也就不足为奇了

因为你只局限于小集合,所以用蛮力来解决它是非常可行的

我们知道至少需要
ceil(N*(N-1)/4)
匹配(因为有
N*(N-1)/2个不同的对,每个匹配最多可以覆盖2个新对)。这给了我们一个算法

import itertools

def mincover(n):
    pairs = set(map(tuple, itertools.combinations(range(n), 2)))
    matches = itertools.combinations(pairs, 2)
    matches = [m for m in matches if not(set(m[0]) & set(m[1]))]
    for subset_size in xrange((len(pairs) + 1) // 2, len(pairs) + 1):
        for subset in itertools.combinations(matches, subset_size):
            cover = set()
            for s in subset: cover |= set(s)
            if cover == pairs:
                return subset

for i in xrange(4, 8):
    print i, mincover(i)

这相当慢,特别是对于6人和7人。这可以通过一个手工编码的搜索来改进,它不考虑不添加新的玩家对的匹配,并且使用对称性,并且总是包括<代码> {{0,1},{2,3}} < /C> >

< P> <强>执行摘要:< /强>

  • 尽管它类似于NP完全最小集覆盖问题,但这个问题远不是难以解决的。特别是,与最小集合覆盖率不同,我们事先知道一个非平凡的最佳可能答案

  • 答案是团队数量除以2(当N个团队为奇数时加1)。我们做得再好不过了

  • 由于问题的结构,有许多可接受的解决方案可以获得最佳答案。您可以使用一个基本的随机贪婪算法偶然发现它们。随着团队数量的增加,您的第一次随机尝试几乎总是成功的

  • 这种方法即使对于大量的团队来说也很快(例如,对于1000个团队,只需几秒钟)

详细信息:

您可以使用公式将k组合转换为d
n_teams = n! / ( (n - k)! k! )

n     n_teams
--    --------
4     6
5     10
6     15
7     21
8     28
9     36
10    45
11    55      # n_teams always equals the sum of values in previous row
min_n_matches = (n_teams + (n_teams % 2)) / 2
import sys
import itertools
import random

def main():
    maxN = int(sys.argv[1])
    for n in range(4, maxN + 1):
        run_scenario(n)

def run_scenario(n):
    # Takes n of players.
    # Generates matches and confirms our expectations.
    k = 2
    players = list(range(1, n + 1))
    teams   = list(set(t) for t in itertools.combinations(players, k))

    # Create the matches, and count how many attempts are needed.
    n_calls = 0
    matches = None
    while matches is None:
        matches = create_matches(teams)
        n_calls += 1

    # Print some info.
    print dict(
        n       = n,
        teams   = len(teams),
        matches = len(matches),
        n_calls = n_calls,
    )

    # Confirm expected N of matches and that all matches are valid.
    T = len(teams)
    assert len(matches) == (T + (T % 2)) / 2
    for t1, t2 in matches:
        assert t1 & t2 == set()

def create_matches(teams):
    # Get a shuffled copy of the list of teams.
    ts = list(teams)
    random.shuffle(ts)

    # Create the matches, greedily.
    matches = []
    while ts:
        # Grab the last team and the first valid opponent.
        t1 = ts.pop()
        t2 = get_opponent(t1, ts)
        # If we did not get a valid opponent and if there are still
        # teams remaining, the greedy matching failed.
        # Otherwise, we must be dealing with an odd N of teams.
        # In that case, pair up the last team with any valid opponent.
        if t2 is None:
            if ts: return None
            else:  t2 = get_opponent(t1, list(teams))
        matches.append((t1, t2))

    return matches

def get_opponent(t1, ts):
    # Takes a team and a list of teams.
    # Search list (from the end) until it finds a valid opponent.
    # Removes opponent from list and returns it.
    for i in xrange(len(ts) - 1, -1, -1):
        if not t1 & ts[i]:
            return ts.pop(i)
    return None

main()
> python volleyball_matches.py 100
{'matches': 3, 'n_calls': 1, 'teams': 6, 'n': 4}
{'matches': 5, 'n_calls': 7, 'teams': 10, 'n': 5}
{'matches': 8, 'n_calls': 1, 'teams': 15, 'n': 6}
{'matches': 11, 'n_calls': 1, 'teams': 21, 'n': 7}
{'matches': 14, 'n_calls': 4, 'teams': 28, 'n': 8}
{'matches': 18, 'n_calls': 1, 'teams': 36, 'n': 9}
{'matches': 23, 'n_calls': 1, 'teams': 45, 'n': 10}
{'matches': 28, 'n_calls': 1, 'teams': 55, 'n': 11}
{'matches': 33, 'n_calls': 1, 'teams': 66, 'n': 12}
...
{'matches': 2186, 'n_calls': 1, 'teams': 4371, 'n': 94}
{'matches': 2233, 'n_calls': 1, 'teams': 4465, 'n': 95}
{'matches': 2280, 'n_calls': 1, 'teams': 4560, 'n': 96}
{'matches': 2328, 'n_calls': 1, 'teams': 4656, 'n': 97}
{'matches': 2377, 'n_calls': 1, 'teams': 4753, 'n': 98}
{'matches': 2426, 'n_calls': 1, 'teams': 4851, 'n': 99}
{'matches': 2475, 'n_calls': 1, 'teams': 4950, 'n': 100}
num_players = gets.to_i
players = (1..num_players).to_a
teams = players.combination(2).to_a
first_half = Float(teams.length / 2.0).ceil
first_half_teams = teams[0..(first_half - 1)]
second_half_teams = teams[first_half..-1]
possible_lineups = []
matches = []
matched = []

first_half_teams.each do |team|
  opponents = second_half_teams.select do |team_2|
    team - team_2 == team
  end
  possible_lineups << [team, opponents]
end

possible_lineups.each do |lineup|
  team_1 = lineup[0]
  team_2 = lineup[1].find do |team|
    !matched.include?(team)
  end
  if !team_2
    thief_team = possible_lineups.find do |test_team|
      test_team[1] - lineup[1] != test_team[1] &&
      test_team[1].find{ |opponent| !matched.include?(opponent) }
    end
    if thief_team
      new_opponent = thief_team[1].find{ |opponent| !matched.include?(opponent) }
      matched << new_opponent
      old_lineup = matches.find do |match|
        match[0] == thief_team[0]
      end
      team_2 = old_lineup[1]
      matches.find{ |match| match[0] == thief_team[0]}[1] = new_opponent
    else
      team_2 = second_half_teams.find do |team|
        lineup[0] - team == lineup[0]
      end
    end
  end
  matches << [team_1, team_2]
  matched << team_2
end

matches.each do |match|
  left_out = players.select{ |player| !match.flatten.include?(player) }
  print match, ", waiting: ", left_out, "\n"
end

print "greater: ", matches.flatten(1).find{ |team| matches.flatten(1).count(team) > teams.count(team) }, "\n"
print "less: ", matches.flatten(1).find{ |team| matches.flatten(1).count(team) < teams.count(team) }, "\n"
4
[[1, 2], [3, 4]], waiting: []
[[1, 3], [2, 4]], waiting: []
[[1, 4], [2, 3]], waiting: []
greater: 
less: 

5
[[1, 2], [3, 5]], waiting: [4]
[[1, 3], [2, 4]], waiting: [5]
[[1, 4], [2, 5]], waiting: [3]
[[1, 5], [3, 4]], waiting: [2]
[[2, 3], [4, 5]], waiting: [1]
greater: 
less: 

6
[[1, 2], [3, 4]], waiting: [5, 6]
[[1, 3], [2, 6]], waiting: [4, 5]
[[1, 4], [3, 5]], waiting: [2, 6]
[[1, 5], [3, 6]], waiting: [2, 4]
[[1, 6], [4, 5]], waiting: [2, 3]
[[2, 3], [4, 6]], waiting: [1, 5]
[[2, 4], [5, 6]], waiting: [1, 3]
[[2, 5], [3, 4]], waiting: [1, 6]
greater: [3, 4]
less: 

7
[[1, 2], [3, 4]], waiting: [5, 6, 7]
[[1, 3], [4, 5]], waiting: [2, 6, 7]
[[1, 4], [3, 5]], waiting: [2, 6, 7]
[[1, 5], [3, 6]], waiting: [2, 4, 7]
[[1, 6], [3, 7]], waiting: [2, 4, 5]
[[1, 7], [4, 6]], waiting: [2, 3, 5]
[[2, 3], [4, 7]], waiting: [1, 5, 6]
[[2, 4], [5, 6]], waiting: [1, 3, 7]
[[2, 5], [6, 7]], waiting: [1, 3, 4]
[[2, 6], [5, 7]], waiting: [1, 3, 4]
[[2, 7], [3, 4]], waiting: [1, 5, 6]
greater: [3, 4]
less: