JavaScript格斗赛事场次排序优化:满足选手场次间隔10-30要求
格斗赛事fightNumber合规分配方案
核心思路
要实现选手相邻赛事编号差10-30的要求,不能用简单的随机或顺序分配,得用**「选手追踪+贪心筛选+回溯兜底」**的策略:先把每个选手的所有赛事归类,然后逐个分配编号时,只选符合间隔要求的赛事,遇到死路就重新来过,确保最终所有约束都满足。
具体实现代码
1. 先给选手的赛事分组
先把同一选手的所有赛事归到一起,方便后续追踪他们的参赛间隔:
// 按选手整理赛事列表 const groupFightsByFighter = (fights) => { const fighterMap = new Map(); fights.forEach(fight => { // 假设每场赛事有redFighter和blueFighter两个选手字段,根据你的实际数据调整 [fight.redFighter, fight.blueFighter].forEach(fighter => { if (!fighterMap.has(fighter)) { fighterMap.set(fighter, []); } fighterMap.get(fighter).push(fight); }); }); return fighterMap; };
2. 核心分配逻辑
从第一场开始,每次只选满足间隔要求的赛事分配编号,没可选的就重新分配:
const assignFightNumbers = (fights) => { const fighterMap = groupFightsByFighter(fights); const assignedFights = new Set(); // 记录已经分配过编号的赛事 const lastNumOfFighter = new Map(); // 记录每个选手最后一场的编号 const result = []; // 随机选第一场赛事启动 let firstFight = fights[Math.floor(Math.random() * fights.length)]; firstFight.fightNumber = 1; assignedFights.add(firstFight); result.push(firstFight); [firstFight.redFighter, firstFight.blueFighter].forEach(fighter => { lastNumOfFighter.set(fighter, 1); }); // 从2到162依次分配编号 for (let num = 2; num <= 162; num++) { // 筛选符合条件的候选赛事:没分配过,且两位选手的上一场编号和当前num的差在10-30之间(或选手还没参赛) const validCandidates = fights.filter(fight => { if (assignedFights.has(fight)) return false; const redValid = !lastNumOfFighter.has(fight.redFighter) || (Math.abs(num - lastNumOfFighter.get(fight.redFighter)) >= 10 && Math.abs(num - lastNumOfFighter.get(fight.redFighter)) <= 30); const blueValid = !lastNumOfFighter.has(fight.blueFighter) || (Math.abs(num - lastNumOfFighter.get(fight.blueFighter)) >= 10 && Math.abs(num - lastNumOfFighter.get(fight.blueFighter)) <= 30); return redValid && blueValid; }); // 没候选就重启分配(简单兜底,复杂场景可以做回溯调整) if (validCandidates.length === 0) { return assignFightNumbers(fights); } // 随机选一个候选分配编号 const selectedFight = validCandidates[Math.floor(Math.random() * validCandidates.length)]; selectedFight.fightNumber = num; assignedFights.add(selectedFight); result.push(selectedFight); // 更新选手的最后参赛编号 [selectedFight.redFighter, selectedFight.blueFighter].forEach(fighter => { lastNumOfFighter.set(fighter, num); }); } return result; };
3. 验证分配结果
分配完记得检查是否符合要求,避免出现遗漏:
const checkAssignValidity = (fights) => { const fighterFightNums = new Map(); fights.forEach(fight => { [fight.redFighter, fight.blueFighter].forEach(fighter => { if (!fighterFightNums.has(fighter)) { fighterFightNums.set(fighter, []); } fighterFightNums.get(fighter).push(fight.fightNumber); }); }); let allValid = true; fighterFightNums.forEach((nums, fighter) => { // 按编号排序后检查相邻差值 nums.sort((a, b) => a - b); for (let i = 1; i < nums.length; i++) { const diff = Math.abs(nums[i] - nums[i-1]); if (diff < 10 || diff > 30) { console.error(`选手${fighter}的相邻赛事编号差为${diff},不符合要求`); allValid = false; } } }); return allValid; };
注意事项
- 如果有选手参赛场次超过6场,162的编号范围下可能刚好卡到极限,建议先检查所有选手的参赛场次,确保理论上有可行解
- 递归重启的方式在极端情况效率不高,可优化成带记忆的回溯算法,或者优先分配场次多的选手的赛事,减少冲突概率
- 实际使用时,要根据你的赛事数据结构调整代码中选手字段的名称(比如你的赛事里选手字段可能不是redFighter/blueFighter)
内容的提问来源于stack exchange,提问作者Dark
相关产品推荐
相关产品推荐

