Algorithm 如何对循环赛进行排序,每个球员休息时间最多?

Algorithm 如何对循环赛进行排序,每个球员休息时间最多?,algorithm,sorting,round-robin,Algorithm,Sorting,Round Robin,循环赛有n名选手。在每一轮比赛中,所有选手都要面对面一次。每轮游戏数为n*n-1/2。轮数不限。比赛一次进行一场,没有休息,所以休息的唯一方法就是不要连续比赛 如何找到具有以下目标的游戏的最佳顺序 最大化最小休息时间,即同一人再次比赛前的游戏次数 尽量减少每轮最小休息次数 把最小的休息时间尽可能平均地分配给队员们 我没有想出任何其他方法来实现这一点,除了暴力的方式:检查每个可能的排列,并将最好的一个保留在内存中 我的场景中有7个人。对于n=7,游戏计数为7*6/2=21,这意味着置换计数为21!

循环赛有n名选手。在每一轮比赛中,所有选手都要面对面一次。每轮游戏数为n*n-1/2。轮数不限。比赛一次进行一场,没有休息,所以休息的唯一方法就是不要连续比赛

如何找到具有以下目标的游戏的最佳顺序

最大化最小休息时间,即同一人再次比赛前的游戏次数 尽量减少每轮最小休息次数 把最小的休息时间尽可能平均地分配给队员们 我没有想出任何其他方法来实现这一点,除了暴力的方式:检查每个可能的排列,并将最好的一个保留在内存中

我的场景中有7个人。对于n=7,游戏计数为7*6/2=21,这意味着置换计数为21!=51090942171709440000. 当然,要检查这么多的排列几乎是不可能的,所以我最终只实现了一个程序,它可以创建多达m的随机列表。这对我当时的目的来说已经足够了。我用这种方法找到的1亿个最佳排列有9个一天休息,它们在不同的玩家中被平均分配

获得最佳排列的最有效方法是什么


对于可能的代码示例,我更喜欢Java、JavaScript或Swift。

有一个相当简单的算法来生成所有对:

把运动员分成两排。在每一轮比赛中,排名靠前的选手都会遇到下一排的相应选手。如果玩家数量为奇数,则一名玩家拥有其余玩家

A B C
D E F
A:D B:E C:F
轮班后,除第一名球员外,所有人都以循环方式轮班

A D B
E F C
A:E D:F B:C 

A E D
F C B
A:F E:C D:B
...
注意:如果我们使用从左到右的游戏顺序,同一个玩家不会参与后续的两个游戏,除了小数字的情况

似乎对于2*N玩家,最小的休息时间是N-1,最长的休息时间是N+1:


生成所有对的算法相当简单:

把运动员分成两排。在每一轮比赛中,排名靠前的选手都会遇到下一排的相应选手。如果玩家数量为奇数,则一名玩家拥有其余玩家

A B C
D E F
A:D B:E C:F
轮班后,除第一名球员外,所有人都以循环方式轮班

A D B
E F C
A:E D:F B:C 

A E D
F C B
A:F E:C D:B
...
注意:如果我们使用从左到右的游戏顺序,同一个玩家不会参与后续的两个游戏,除了小数字的情况

似乎对于2*N玩家,最小的休息时间是N-1,最长的休息时间是N+1:


与尝试随机排列不同,我们可以应用有偏差的随机密钥遗传算法BRKGA。这种通用优化技术可以找到n=7的解决方案,其中只有四个一场比赛休息,所有休息都由不同的玩家进行:

1 4 1
1 3
0 4
2 5
1 6
2 3
4 5
0 6
3 5
1 2
4 6
0 3
1 5
2 6
3 4
0 1
2 4
3 6
0 5
1 4
0 2
5 6
C++代码:

#include <algorithm>
#include <array>
#include <iostream>
#include <limits>
#include <numeric>
#include <random>
#include <tuple>
#include <utility>
#include <vector>

namespace {

constexpr int kNumPlayers = 7;
constexpr int kNumMatches = kNumPlayers * (kNumPlayers - 1) / 2;

class Solution {
public:
  template <typename Generator> static Solution Random(Generator &generator);

  template <typename Generator>
  Solution MateWith(const Solution &that, Generator &generator) const;

  std::array<std::tuple<int, int>, kNumMatches> Matches() const;

private:
  Solution() = default;

  std::array<double, kNumMatches> keys_;
};

template <typename Generator> Solution Solution::Random(Generator &generator) {
  Solution solution;
  std::uniform_real_distribution<double> uniform;
  for (int k = 0; k < kNumMatches; k++) {
    solution.keys_[k] = uniform(generator);
  }
  return solution;
}

template <typename Generator>
Solution Solution::MateWith(const Solution &that, Generator &generator) const {
  Solution child;
  std::bernoulli_distribution biased_coin(0.7);
  for (int k = 0; k < kNumMatches; k++) {
    child.keys_[k] = biased_coin(generator) ? this->keys_[k] : that.keys_[k];
  }
  return child;
}

std::array<std::tuple<int, int>, kNumMatches> Solution::Matches() const {
  std::array<std::tuple<double, std::tuple<int, int>>, kNumMatches> rankings;
  {
    int k = 0;
    for (int i = 0; i < kNumPlayers; i++) {
      for (int j = i + 1; j < kNumPlayers; j++) {
        rankings[k] = {keys_[k], {i, j}};
        k++;
      }
    }
  }
  std::sort(rankings.begin(), rankings.end());
  std::array<std::tuple<int, int>, kNumMatches> matches;
  for (int k = 0; k < kNumMatches; k++) {
    matches[k] = std::get<1>(rankings[k]);
  }
  return matches;
}

std::vector<std::tuple<int, int>>
Rests(const std::array<std::tuple<int, int>, kNumMatches> &matches) {
  std::array<int, kNumMatches> last_match;
  for (int k = 0; k < kNumMatches; k++) {
    last_match[std::get<0>(matches[k])] = k - kNumMatches;
    last_match[std::get<1>(matches[k])] = k - kNumMatches;
  }
  std::vector<std::tuple<int, int>> rests;
  for (int k = 0; k < kNumMatches; k++) {
    auto plays = [&](int i) {
      rests.push_back({k - 1 - last_match[i], i});
      last_match[i] = k;
    };
    plays(std::get<0>(matches[k]));
    plays(std::get<1>(matches[k]));
  }
  return rests;
}

std::tuple<int, int, int>
Objective(const std::array<std::tuple<int, int>, kNumMatches> &matches) {
  auto rests = Rests(matches);
  int min_rest = std::get<0>(*std::min_element(rests.begin(), rests.end()));
  std::array<int, kNumPlayers> player_to_min_rest_count;
  std::fill(player_to_min_rest_count.begin(), player_to_min_rest_count.end(),
            0);
  for (auto [rest, player] : rests) {
    if (rest == min_rest) {
      player_to_min_rest_count[player]++;
    }
  }
  return {-min_rest,
          std::accumulate(player_to_min_rest_count.begin(),
                          player_to_min_rest_count.end(), 0),
          *std::max_element(player_to_min_rest_count.begin(),
                            player_to_min_rest_count.end())};
}

std::vector<Solution> SortByFitness(const std::vector<Solution> &population) {
  std::vector<std::tuple<std::tuple<int, int, int>, const Solution *>> tagged;
  tagged.reserve(population.size());
  for (const Solution &solution : population) {
    tagged.push_back({Objective(solution.Matches()), &solution});
  }
  std::sort(tagged.begin(), tagged.end());
  std::vector<Solution> sorted_population;
  sorted_population.reserve(population.size());
  for (auto [objective, solution] : tagged) {
    sorted_population.push_back(*solution);
  }
  return sorted_population;
}

template <typename Generator> Solution BRKGA(Generator &generator) {
  static constexpr int kRounds = 20000;
  static constexpr int kNumEliteSolutions = 300;
  static constexpr int kNumMatedSolutions = 600;
  static constexpr int kNumRandomSolutions = 100;
  static constexpr int kNumSolutions =
      kNumEliteSolutions + kNumMatedSolutions + kNumRandomSolutions;
  std::vector<Solution> population;
  population.reserve(kNumSolutions);
  for (int i = 0; i < kNumSolutions; i++) {
    population.push_back(Solution::Random(generator));
  }
  for (int r = 0; r < kRounds; r++) {
    population = SortByFitness(population);
    std::vector<Solution> new_population;
    new_population.reserve(kNumSolutions);
    for (int i = 0; i < kNumEliteSolutions; i++) {
      new_population.push_back(population[i]);
    }
    std::uniform_int_distribution<int> elite(0, kNumEliteSolutions - 1);
    std::uniform_int_distribution<int> non_elite(kNumEliteSolutions,
                                                 kNumSolutions - 1);
    for (int i = 0; i < kNumMatedSolutions; i++) {
      int j = elite(generator);
      int k = non_elite(generator);
      new_population.push_back(
          population[j].MateWith(population[k], generator));
    }
    for (int i = 0; i < kNumRandomSolutions; i++) {
      new_population.push_back(Solution::Random(generator));
    }
    population = std::move(new_population);
  }
  return SortByFitness(population)[0];
}

void PrintSolution(const Solution &solution) {
  auto matches = solution.Matches();
  auto objective = Objective(matches);
  std::cout << -std::get<0>(objective) << ' ' << std::get<1>(objective) << ' '
            << std::get<2>(objective) << '\n';
  for (auto [i, j] : solution.Matches()) {
    std::cout << i << ' ' << j << '\n';
  }
}

} // namespace

int main() {
  std::default_random_engine generator;
  PrintSolution(BRKGA(generator));
}

与尝试随机排列不同,我们可以应用有偏差的随机密钥遗传算法BRKGA。这种通用优化技术可以找到n=7的解决方案,其中只有四个一场比赛休息,所有休息都由不同的玩家进行:

1 4 1
1 3
0 4
2 5
1 6
2 3
4 5
0 6
3 5
1 2
4 6
0 3
1 5
2 6
3 4
0 1
2 4
3 6
0 5
1 4
0 2
5 6
C++代码:

#include <algorithm>
#include <array>
#include <iostream>
#include <limits>
#include <numeric>
#include <random>
#include <tuple>
#include <utility>
#include <vector>

namespace {

constexpr int kNumPlayers = 7;
constexpr int kNumMatches = kNumPlayers * (kNumPlayers - 1) / 2;

class Solution {
public:
  template <typename Generator> static Solution Random(Generator &generator);

  template <typename Generator>
  Solution MateWith(const Solution &that, Generator &generator) const;

  std::array<std::tuple<int, int>, kNumMatches> Matches() const;

private:
  Solution() = default;

  std::array<double, kNumMatches> keys_;
};

template <typename Generator> Solution Solution::Random(Generator &generator) {
  Solution solution;
  std::uniform_real_distribution<double> uniform;
  for (int k = 0; k < kNumMatches; k++) {
    solution.keys_[k] = uniform(generator);
  }
  return solution;
}

template <typename Generator>
Solution Solution::MateWith(const Solution &that, Generator &generator) const {
  Solution child;
  std::bernoulli_distribution biased_coin(0.7);
  for (int k = 0; k < kNumMatches; k++) {
    child.keys_[k] = biased_coin(generator) ? this->keys_[k] : that.keys_[k];
  }
  return child;
}

std::array<std::tuple<int, int>, kNumMatches> Solution::Matches() const {
  std::array<std::tuple<double, std::tuple<int, int>>, kNumMatches> rankings;
  {
    int k = 0;
    for (int i = 0; i < kNumPlayers; i++) {
      for (int j = i + 1; j < kNumPlayers; j++) {
        rankings[k] = {keys_[k], {i, j}};
        k++;
      }
    }
  }
  std::sort(rankings.begin(), rankings.end());
  std::array<std::tuple<int, int>, kNumMatches> matches;
  for (int k = 0; k < kNumMatches; k++) {
    matches[k] = std::get<1>(rankings[k]);
  }
  return matches;
}

std::vector<std::tuple<int, int>>
Rests(const std::array<std::tuple<int, int>, kNumMatches> &matches) {
  std::array<int, kNumMatches> last_match;
  for (int k = 0; k < kNumMatches; k++) {
    last_match[std::get<0>(matches[k])] = k - kNumMatches;
    last_match[std::get<1>(matches[k])] = k - kNumMatches;
  }
  std::vector<std::tuple<int, int>> rests;
  for (int k = 0; k < kNumMatches; k++) {
    auto plays = [&](int i) {
      rests.push_back({k - 1 - last_match[i], i});
      last_match[i] = k;
    };
    plays(std::get<0>(matches[k]));
    plays(std::get<1>(matches[k]));
  }
  return rests;
}

std::tuple<int, int, int>
Objective(const std::array<std::tuple<int, int>, kNumMatches> &matches) {
  auto rests = Rests(matches);
  int min_rest = std::get<0>(*std::min_element(rests.begin(), rests.end()));
  std::array<int, kNumPlayers> player_to_min_rest_count;
  std::fill(player_to_min_rest_count.begin(), player_to_min_rest_count.end(),
            0);
  for (auto [rest, player] : rests) {
    if (rest == min_rest) {
      player_to_min_rest_count[player]++;
    }
  }
  return {-min_rest,
          std::accumulate(player_to_min_rest_count.begin(),
                          player_to_min_rest_count.end(), 0),
          *std::max_element(player_to_min_rest_count.begin(),
                            player_to_min_rest_count.end())};
}

std::vector<Solution> SortByFitness(const std::vector<Solution> &population) {
  std::vector<std::tuple<std::tuple<int, int, int>, const Solution *>> tagged;
  tagged.reserve(population.size());
  for (const Solution &solution : population) {
    tagged.push_back({Objective(solution.Matches()), &solution});
  }
  std::sort(tagged.begin(), tagged.end());
  std::vector<Solution> sorted_population;
  sorted_population.reserve(population.size());
  for (auto [objective, solution] : tagged) {
    sorted_population.push_back(*solution);
  }
  return sorted_population;
}

template <typename Generator> Solution BRKGA(Generator &generator) {
  static constexpr int kRounds = 20000;
  static constexpr int kNumEliteSolutions = 300;
  static constexpr int kNumMatedSolutions = 600;
  static constexpr int kNumRandomSolutions = 100;
  static constexpr int kNumSolutions =
      kNumEliteSolutions + kNumMatedSolutions + kNumRandomSolutions;
  std::vector<Solution> population;
  population.reserve(kNumSolutions);
  for (int i = 0; i < kNumSolutions; i++) {
    population.push_back(Solution::Random(generator));
  }
  for (int r = 0; r < kRounds; r++) {
    population = SortByFitness(population);
    std::vector<Solution> new_population;
    new_population.reserve(kNumSolutions);
    for (int i = 0; i < kNumEliteSolutions; i++) {
      new_population.push_back(population[i]);
    }
    std::uniform_int_distribution<int> elite(0, kNumEliteSolutions - 1);
    std::uniform_int_distribution<int> non_elite(kNumEliteSolutions,
                                                 kNumSolutions - 1);
    for (int i = 0; i < kNumMatedSolutions; i++) {
      int j = elite(generator);
      int k = non_elite(generator);
      new_population.push_back(
          population[j].MateWith(population[k], generator));
    }
    for (int i = 0; i < kNumRandomSolutions; i++) {
      new_population.push_back(Solution::Random(generator));
    }
    population = std::move(new_population);
  }
  return SortByFitness(population)[0];
}

void PrintSolution(const Solution &solution) {
  auto matches = solution.Matches();
  auto objective = Objective(matches);
  std::cout << -std::get<0>(objective) << ' ' << std::get<1>(objective) << ' '
            << std::get<2>(objective) << '\n';
  for (auto [i, j] : solution.Matches()) {
    std::cout << i << ' ' << j << '\n';
  }
}

} // namespace

int main() {
  std::default_random_engine generator;
  PrintSolution(BRKGA(generator));
}

您是否实现了循环移位循环算法?你知道吗?我没有实现任何算法,我只是将顺序随机m次,然后检查随机顺序是否满足我的标准。在做了1亿次之后,最好的结果是令人满意的,但当然,可能还有更好的结果没有被发现。我不知道循环移位算法。你能链接到一些关于它的文章吗?我没有通过快速搜索找到任何相关内容。一次只能玩一个游戏吗?是的,游戏是连续的。你实现了循环移位循环算法吗?你知道吗?我没有实现任何算法,我只是将顺序随机m次,然后检查随机顺序是否满足我的标准。在做了1亿次之后,最好的结果是令人满意的,但当然,可能还有更好的结果没有被发现。我不知道循环移位算法。你能链接到一些关于它的文章吗?通过快速搜索,我没有找到任何相关内容。一次只能玩一个游戏吗?是的,游戏是连续的。这似乎是配对的好方法。然而,根据我的第二个标准,它不是最好的。对于n=7,最小休息时间为1天,但计数为10天。我只需尝试随机点餐就可以达到9点。好的是,这种方法确实比随机顺序更平均地分配了玩家之间的休息时间。每位选手每天最多休息2次。这似乎是一个很好的配对方式。然而,根据我的第二个标准,它不是最好的。对于n=7,最小休息时间为1天,但计数为10天。我只需尝试随机点餐就可以达到9点。好的是,这种方法确实比随机顺序更平均地分配了玩家之间的休息时间。每个玩家每天最多休息2次。如果有无限轮,这种算法是否也有效?在这种情况下,玩家5将有两个连续的游戏,最后一个和第一个。@KrohnicDev是的,我更新了代码。这种游戏会不会
如果有无限轮,f算法也能工作吗?在这种情况下,玩家5将有两个连续的最后和第一场比赛。@KrohnicDev是的,我更新了代码。