如何为已用usort排序的玩家数组计算从高到低的密集排名
实现带并列排名的玩家分数排序方案
嘿,这个需求其实挺常见的,我来给你拆解下最优的实现思路,保证逻辑清晰还高效。
首先核心步骤就两步:先按分数降序排序数组,再遍历计算并列排名。咱们一步步来:
第一步:排序玩家数组
既然你已经熟悉usort,那先把玩家数组按分数从高到低排好序,这是计算排名的基础:
$players = [ ['name' => 'Nick', 'score' => 25], ['name' => 'Tom', 'score' => 18], ['name' => 'Chris', 'score' => 18], ['name' => 'Dave', 'score' => 16], ['name' => 'James', 'score' => 8], ]; // 用PHP7+的太空船运算符实现降序排序,比传统if-else简洁 usort($players, function($a, $b) { return $b['score'] <=> $a['score']; });
第二步:计算并列排名(两种最优方案)
方案一:遍历动态计算(内存高效,适合大数据量)
这种方法不需要额外的中间数组,只需要一次遍历就能完成排名计算,内存占用极低。我们只需要跟踪当前排名、上一个玩家的分数,以及是否处于并列状态:
$rankedPlayers = []; $currentRank = 1; $previousScore = null; // 标记当前是否处于并列状态 $isTie = false; foreach ($players as $index => $player) { if ($index === 0) { // 第一个玩家直接是第1名 $rankText = "第{$currentRank}名"; } else { if ($player['score'] === $previousScore) { // 和上一个分数相同,排名不变,标记并列 $rankText = "(并列)第{$currentRank}名"; $isTie = true; } else { // 遇到新的更低分数,排名+1,重置并列标记 $currentRank++; $rankText = "第{$currentRank}名"; $isTie = false; } } $previousScore = $player['score']; $rankedPlayers[] = [ 'name' => $player['name'], 'score' => $player['score'], 'rank' => $rankText ]; } // 输出结果 foreach ($rankedPlayers as $rp) { echo "{$rp['name']} - {$rp['rank']}\n"; }
输出结果完全符合你的需求:
Nick - 第1名
Tom - (并列)第2名
Chris - (并列)第2名
Dave - 第3名
James - 第4名
方案二:分数映射法(逻辑直观,适合小数据量)
如果你的玩家数量不多,这种方法代码更简洁,逻辑一眼就能看懂:
- 先提取所有唯一分数并降序排序,建立「分数→排名」的映射
- 统计每个分数的玩家数量,判断是否需要标记并列
// 提取唯一分数并降序排序 $uniqueScores = array_unique(array_column($players, 'score')); rsort($uniqueScores); // 建立分数到排名的映射(排名=索引+1) $scoreToRank = array_combine($uniqueScores, range(1, count($uniqueScores))); // 统计每个分数的玩家数量 $scoreCount = array_count_values(array_column($players, 'score')); // 生成带排名的玩家数组 $rankedPlayers = array_map(function($player) use ($scoreToRank, $scoreCount) { $rank = $scoreToRank[$player['score']]; $rankText = $scoreCount[$player['score']] > 1 ? "(并列)第{$rank}名" : "第{$rank}名"; return [ 'name' => $player['name'], 'score' => $player['score'], 'rank' => $rankText ]; }, $players);
为什么这两种是最优方案?
不管哪种方案,时间复杂度都是O(n log n)(主要来自排序步骤),后续的遍历/映射操作都是O(n)的线性时间——这已经是你能拿到的最优复杂度了,因为排序是计算排名的必要步骤,不可能绕过。
- 如果追求低内存,选遍历动态计算,不需要额外存储中间数据;
- 如果想代码更简洁易读,选分数映射法,适合玩家数量不多的场景。
内容的提问来源于stack exchange,提问作者Nick
相关产品推荐
相关产品推荐

