Algorithm 如何对循环赛进行排序,每个球员休息时间最多?
循环赛有n名选手。在每一轮比赛中,所有选手都要面对面一次。每轮游戏数为n*n-1/2。轮数不限。比赛一次进行一场,没有休息,所以休息的唯一方法就是不要连续比赛 如何找到具有以下目标的游戏的最佳顺序 最大化最小休息时间,即同一人再次比赛前的游戏次数 尽量减少每轮最小休息次数 把最小的休息时间尽可能平均地分配给队员们 我没有想出任何其他方法来实现这一点,除了暴力的方式:检查每个可能的排列,并将最好的一个保留在内存中 我的场景中有7个人。对于n=7,游戏计数为7*6/2=21,这意味着置换计数为21!=51090942171709440000. 当然,要检查这么多的排列几乎是不可能的,所以我最终只实现了一个程序,它可以创建多达m的随机列表。这对我当时的目的来说已经足够了。我用这种方法找到的1亿个最佳排列有9个一天休息,它们在不同的玩家中被平均分配 获得最佳排列的最有效方法是什么Algorithm 如何对循环赛进行排序,每个球员休息时间最多?,algorithm,sorting,round-robin,Algorithm,Sorting,Round Robin,循环赛有n名选手。在每一轮比赛中,所有选手都要面对面一次。每轮游戏数为n*n-1/2。轮数不限。比赛一次进行一场,没有休息,所以休息的唯一方法就是不要连续比赛 如何找到具有以下目标的游戏的最佳顺序 最大化最小休息时间,即同一人再次比赛前的游戏次数 尽量减少每轮最小休息次数 把最小的休息时间尽可能平均地分配给队员们 我没有想出任何其他方法来实现这一点,除了暴力的方式:检查每个可能的排列,并将最好的一个保留在内存中 我的场景中有7个人。对于n=7,游戏计数为7*6/2=21,这意味着置换计数为21!
对于可能的代码示例,我更喜欢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是的,我更新了代码。