You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为已用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名

方案二:分数映射法(逻辑直观,适合小数据量)

如果你的玩家数量不多,这种方法代码更简洁,逻辑一眼就能看懂:

  1. 先提取所有唯一分数并降序排序,建立「分数→排名」的映射
  2. 统计每个分数的玩家数量,判断是否需要标记并列
// 提取唯一分数并降序排序
$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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:08:28