如何以编程方式将团队时间分组到团队中以最小化差异?

时间:2017-06-30 11:03:20

标签: python algorithm

考虑到以下(任意)单圈时间:

John: 47.20
Mark: 51.14
Shellie: 49.95
Scott: 48.80
Jack: 46.60
Cheryl: 52.70
Martin: 57.65
Karl: 55.45
Yong: 52.30
Lynetta: 59.90
Sueann: 49.24
Tempie: 47.88
Mack: 51.11
Kecia: 53.20
Jayson: 48.90
Sanjuanita: 45.90
Rosita: 54.43
Lyndia: 52.38
Deloris: 49.90
Sophie: 44.31
Fleta: 58.12
Tai: 61.23
Cassaundra: 49.38 
Oren: 48.39

我们正在进行卡丁车耐力赛,而不是允许团队挑选的想法是编写一个工具来处理最初的资格赛时间,然后吐出最匹配的分组。

我的初步调查让我觉得这是一个集团图形类型的情况,但从未玩过图形算法,我感觉不太深入。

最快/最简单的方法是生成最接近整体平均单圈时间的3人组,以便消除他们之间的整体优势/差异?

这是我可以使用networkx来实现的吗?如果是这样,我将如何在给定上述数据集的情况下最好地定义图形?

4 个答案:

答案 0 :(得分:2)

如果我理解正确,只需对时间列表进行排序,然后将前三个,后三个,分组排在前三位。

编辑:我没有正确理解

所以,我们的想法是让N人进入N / 3队,将N / 3队的平均时间[而不是我错误解释的每队中的3人]尽可能接近。在这种情况下,我认为您仍然可以按递减顺序对N个驱动程序进行排序。然后,初始化一个空的N / 3队列表。然后对于每个驾驶员按照单圈时间的降序排列,将他们分配给当前总圈数最小的球队(如果是平局,则分配给其中一支球队)。这是简单的bin打包算法的变体。

这是一个简单的Python实现:

times = [47.20, 51.14, 49.95, 48.80, 46.60, 52.70, 57.65, 55.45, 52.30, 59.90, 49.24, 47.88, 51.11, 53.20, 48.90, 45.90, 54.43, 52.38, 49.90, 44.31, 58.12, 61.23, 49.38, 48.39]

Nteams = len(times)/3
team_times = [0] * Nteams
team_members = [[]] * Nteams

times = sorted(times,reverse=True)
for m in range(len(times)):
    i = team_times.index(min(team_times))
    team_times[i] += times[m]
    team_members[i] = team_members[i] + [m]

for i in range(len(team_times)):
    print(str(team_members[i]) + ": avg time " + str(round(team_times[i]/3,3)))

,其输出为

[0, 15, 23]: avg time 51.593
[1, 14, 22]: avg time 51.727
[2, 13, 21]: avg time 51.54
[3, 12, 20]: avg time 51.6
[4, 11, 19]: avg time 51.48
[5, 10, 18]: avg time 51.32
[6, 9, 17]: avg time 51.433
[7, 8, 16]: avg time 51.327

(请注意,团队成员编号按照单圈时间的降序从0开始,而不是原始顺序)。

这个问题的一个问题是,如果时间变化很大,那么每个队伍中的球员数量就没有严格的限制。但是,为了你的目的,也许没关系,如果这样做的话继电器关闭,并且当时间的传播远小于平均时间时,它可能是罕见的。

修改的 如果你只想在每支球队中拥有3名球员,那么在每一步中都可以轻松修改代码,以便在每个步骤中找到总圈数最少的球队,而这个球队已经没有三名指定的球员。这需要在主代码块中进行少量修改:

times = sorted(times,reverse=True)
for m in range(len(times)):
    idx = -1
    for i in range(Nteams):
        if len(team_members[i]) < 3:
            if (idx == -1) or (team_times[i] < team_times[idx]):
                idx = i
    team_times[idx] += times[m]
    team_members[idx] = team_members[idx] + [m]

对于问题中的示例问题,上述解决方案当然是相同的,因为它并没有尝试每个团队适合多于或少于3个玩家。

答案 1 :(得分:2)

当你遇到这样的问题时,一种方法总是利用随机性。

虽然其他人说他们认为 X或Y应该有效,但我知道我的算法会收敛到至少一个局部最大值。如果您可以通过成对交换显示可以从任何其他状态空间到达任何状态空间(例如,旅行销售员问题的属性),则算法将找到全局最优(给定时间)。

此外,该算法尝试最小化组间平均时间的标准差,因此它提供了一个自然的度量标准,表明您得到的答案有多好:即使结果不准确,也会得到标准偏差为了您的目的,0.058可能已足够接近。

换句话说:可能有一个确切的解决方案,但随机解决方案通常很容易想象,代码不需要很长时间,可以很好地收敛,并且能够产生可接受的答案。

#!/usr/bin/env python3

import numpy as np
import copy
import random

data = [
  (47.20,"John"),
  (51.14,"Mark"),
  (49.95,"Shellie"),
  (48.80,"Scott"),
  (46.60,"Jack"),
  (52.70,"Cheryl"),
  (57.65,"Martin"),
  (55.45,"Karl"),
  (52.30,"Yong"),
  (59.90,"Lynetta"),
  (49.24,"Sueann"),
  (47.88,"Tempie"),
  (51.11,"Mack"),
  (53.20,"Kecia"),
  (48.90,"Jayson"),
  (45.90,"Sanjuanita"),
  (54.43,"Rosita"),
  (52.38,"Lyndia"),
  (49.90,"Deloris"),
  (44.31,"Sophie"),
  (58.12,"Fleta"),
  (61.23,"Tai"),
  (49.38 ,"Cassaundra"),
  (48.39,"Oren")
]

#Divide into initial groupings
NUM_GROUPS = 8
groups = []
for x in range(NUM_GROUPS): #Number of groups desired
  groups.append(data[x*len(data)//NUM_GROUPS:(x+1)*len(data)//NUM_GROUPS])

#Ensure all groups have the same number of members
assert all(len(groups[0])==len(x) for x in groups)

#Get average time of a single group
def FitnessGroup(group): 
  return np.average([x[0] for x in group])

#Get standard deviation of all groups' average times
def Fitness(groups):
  avgtimes = [FitnessGroup(x) for x in groups] #Get all average times
  return np.std(avgtimes) #Return standard deviation of average times

#Initially, the best grouping is just the data
bestgroups  = copy.deepcopy(groups)
bestfitness = Fitness(groups)

#Generate mutations of the best grouping by swapping two randomly chosen members
#between their groups
for x in range(10000): #Run a large number of times
  groups = copy.deepcopy(bestgroups)       #Always start from the best grouping
  g1 = random.randint(0,len(groups)-1)     #Choose a random group A
  g2 = random.randint(0,len(groups)-1)     #Choose a random group B
  m1 = random.randint(0,len(groups[g1])-1) #Choose a random member from group A
  m2 = random.randint(0,len(groups[g2])-1) #Choose a random member from group B
  groups[g1][m1], groups[g2][m2] = groups[g2][m2], groups[g1][m1] #Swap 'em
  fitness = Fitness(groups)                #Calculate fitness of new grouping
  if fitness<bestfitness:                  #Is it a better fitness?
    bestfitness = fitness                  #Save fitness
    bestgroups  = copy.deepcopy(groups)    #Save grouping

#Print the results
for g in bestgroups:
  for m in g:
    print("{0:15}".format(m[1]), end='') 
  print("{0:15.3f}".format(FitnessGroup(g)), end='')
  print("")
print("Standard deviation of teams: {0:.3f}".format(bestfitness))

运行这几次会产生0.058的标准偏差:

Cheryl         Kecia          Oren                    51.430
Tempie         Mark           Karl                    51.490
Fleta          Deloris        Jack                    51.540
Lynetta        Scott          Sanjuanita              51.533
Mack           Rosita         Sueann                  51.593
Shellie        Lyndia         Yong                    51.543
Jayson         Sophie         Tai                     51.480
Martin         Cassaundra     John                    51.410
Standard deviation of teams: 0.058

答案 2 :(得分:1)

以下算法似乎运行良好。它需要剩下最快和最慢的人,然后找到中间的人,以便组平均值最接近全球平均值。由于极端值首先被用尽,尽管选择池有限,但最终的平均值不应该那么远。

from bisect import bisect

times = sorted([47.20, 51.14, 49.95, 48.80, 46.60, 52.70, 57.65, 55.45, 52.30, 59.90, 49.24, 47.88, 51.11, 53.20, 48.90, 45.90, 54.43, 52.38, 49.90, 44.31, 58.12, 61.23, 49.38, 48.39])
average = lambda c: sum(c)/len(c)

groups = []
average_time = average(times)

while times:
    group = [times.pop(0), times.pop()]

    # target value for the third person for best average
    target = average_time * 3 - sum(group)
    index = min(bisect(times, target), len(times) - 1)

    # adjust if the left value is better than the right
    if index and abs(target - times[index-1]) < abs(target - times[index]):
        index -= 1

    group.append(times.pop(index))
    groups.append(group)

# [44.31, 61.23, 48.9]
# [45.9, 59.9, 48.8]
# [46.6, 58.12, 49.9]
# [47.2, 57.65, 49.38]
# [47.88, 55.45, 51.14]
# [48.39, 54.43, 51.11]
# [49.24, 53.2, 52.3]
# [49.95, 52.7, 52.38]

排序和迭代二进制搜索都是O(n log n),因此总复杂度为O(n log n)。不幸的是,将其扩展到更大的群体可能会很困难。

答案 3 :(得分:0)

最简单的可能只是创建3个桶 - 一个快速桶,一个中型桶和一个慢桶 - 并根据它们的合格时间为桶分配条目。

然后将最慢的,最快的,以及媒介的中位数或平均值组合在一起。 (不确定中位数或平均数是否是最好的选择。)重复直到你没有参赛作品。