NumPy: 排序搜索去重

难度:⭐⭐ | 关键词np.sort, argsort, lexsort, searchsorted, unique

Alice 拿到全班成绩表,想按总分排名——但不能丢掉学号!np.argsort() 返回的是“排好序的索引”,而非排好序的数据。它才是排序的灵魂:告诉你第 1 名原来是第几号。


1. np.sort — 快速排序

np.sort(a, axis=-1, kind='quicksort', order=None) 返回副本,不修改原数组。

参数 说明
axis 排序轴,默认最后一轴
kind 'quicksort''mergesort''heapsort''stable'
order 结构化数组的排序字段

(1) 排序算法对比

算法 时间复杂度 稳定性 特点
quicksort O(n log n) 平均 不稳定 默认,速度快
mergesort O(n log n) 稳定 额外内存,保序场景
heapsort O(n log n) 不稳定 内存友好
stable O(n log n) 稳定 内部用 timsort/mergesort

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

:sort 多种算法(难度⭐)

PYTHON
import numpy as np

a = np.array([3, 1, 2, 1, 4])

# 默认 quicksort
print(np.sort(a))                    # [1 1 2 3 4]

# stable:等值元素保持原始顺序
names = np.array(['Carol', 'Bob', 'Alice', 'Bob'])
scores = np.array([85, 90, 90, 70])
dtype = [('name', 'U10'), ('score', int)]
students = np.array(list(zip(names, scores)), dtype=dtype)

print(np.sort(students, order='score', kind='stable'))
# [('Bob', 70) ('Carol', 85) ('Bob', 90) ('Alice', 90)]
# Carol 在 Alice 前面——保持了原始相对顺序

# 按列排序 2D 数组
b = np.array([[3, 1, 2],
              [6, 4, 5]])
print(np.sort(b, axis=0))
# [[3 1 2]
#  [6 4 5]]
print(np.sort(b, axis=1))
# [[1 2 3]
#  [4 5 6]]

# 原数组未被修改
print(a)  # [3 1 2 1 4]
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

2. argsort — 排序索引

.argsort() 是排序的灵魂——告诉你第 1 名是原来第几号。

(1) sort vs argsort vs lexsort

函数 返回值 用途
np.sort(a) 排好序的数据副本 只需要排序结果
np.argsort(a) 排好序的索引数组 用索引同步排列其他数组
np.lexsort(keys) 多键排序的索引 多条件排序

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

:argsort(难度⭐)

PYTHON
import numpy as np

scores = np.array([88, 95, 72, 95, 60])

# argsort 返回索引
rank_idx = np.argsort(scores)
print(rank_idx)            # [4 2 0 1 3]

# 用索引取排好序的值
print(scores[rank_idx])    # [60 72 88 95 95]

# 降序:切片反转
print(np.argsort(scores)[::-1])  # [1 3 0 2 4]

# 同步排序:用 argsort 索引重排另一个数组
names = np.array(['Alice', 'Bob', 'Charlie', 'Carol', 'David'])
idx = np.argsort(scores)
print(names[idx])  # ['David' 'Charlie' 'Alice' 'Bob' 'Carol']
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

(2) argsort 流程图

100%
graph LR
    A["原数组<br/>[88, 95, 72, 95, 60]"] --> B["argsort"]
    B --> C["索引数组<br/>[4, 2, 0, 1, 3]"]
    C --> D["用索引取值<br/>scores[idx]"]
    D --> E["排序结果<br/>[60, 72, 88, 95, 95]"]
    style B fill:#f9f,stroke:#333
    style C fill:#bbf,stroke:#333
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

3. lexsort — 多键排序

np.lexsort(keys)keys最后一层第一层排列——最后传入的键优先级最高。

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

:lexsort 多键排序(难度⭐⭐)

PYTHON
import numpy as np

# 先按姓排序,姓相同按名排序
surname = np.array(['Zhang', 'Li', 'Zhang', 'Li', 'Zhang'])
given    = np.array(['Alice', 'Bob', 'Charlie', 'Alice', 'Bob'])

# lexsort:最后传入的键为主键
idx = np.lexsort((given, surname))
print(idx)                          # [3 1 0 4 2]
print(surname[idx])                 # ['Li' 'Li' 'Zhang' 'Zhang' 'Zhang']
print(given[idx])                   # ['Alice' 'Bob' 'Alice' 'Bob' 'Charlie']

# 等价写法:用列表,最后一个元素是主键
idx2 = np.lexsort([given, surname])
print(np.array_equal(idx, idx2))    # True
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

4. partition — 部分排序

只需要第 k 小的值(或前 k 小)时,np.partitionnp.sort 更快——O(n) vs O(n log n)。

(1) partition 和 sort 区别

特性 np.sort np.partition
复杂度 O(n log n) O(n)
结果 全部有序 第 k 位置正确,两侧不保证有序
适用场景 需要完整排序 只需 Top-K 或中位数

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

:partition 取前 k 小(难度⭐⭐)

PYTHON
import numpy as np

a = np.array([7, 2, 5, 1, 8, 3, 6, 4])

# 第 3 小的值放在索引 2,左侧都比它小,右侧都比它大
p = np.partition(a, 3)
print(p)        # [2 1 3 4 8 5 6 7]  <- a[2]=3 是第 3 小
print(p[:3])    # [2 1 3]  <- 前 3 小(但不保证有序)

# argpartition:返回 partition 的索引
idx = np.argpartition(a, 3)
print(a[idx])   # 同 partition 结果
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

5. searchsorted — 二分查找

np.searchsorted(sorted_array, values)已排序数组中找到插入位置,O(log n)。

(1) 搜索方法对比

方法 数组要求 返回值 复杂度
np.searchsorted 必须排序 插入位置索引 O(log n)
np.where 无限制 满足条件的索引 O(n)
np.argmax/argmin 无限制 最值索引 O(n)

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

:searchsorted(难度⭐)

PYTHON
import numpy as np

arr = np.array([10, 20, 30, 40, 50])

# 左侧插入位置(默认 side='left')
print(np.searchsorted(arr, 25))   # 2 <- 25 插在索引 2

# 右侧插入位置
print(np.searchsorted(arr, 30, side='right'))  # 3

# 批量查找
vals = np.array([5, 25, 30, 55])
print(np.searchsorted(arr, vals))  # [0 2 2 5]

# 实际应用:判断值落在哪个区间
bins = np.array([0, 60, 80, 100])
scores = np.array([45, 72, 95, 60])
grade_idx = np.searchsorted(bins, scores, side='right') - 1
print(grade_idx)  # [0 1 2 1]
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

6. unique — 去重计数

np.unique(ar, return_index=False, return_inverse=False, return_counts=False)

(1) unique vs bincount

函数 输入要求 返回 适用场景
np.unique 任意类型 去重值 + 可选计数 通用去重、频次统计
np.bincount 非负整数 每个整数的出现次数 整数频次统计,速度更快

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

:unique 计数(难度⭐)

PYTHON
import numpy as np

a = np.array([3, 1, 2, 1, 3, 2, 2])

# 基本去重(自动排序)
vals, counts = np.unique(a, return_counts=True)
print(vals)    # [1 2 3]
print(counts)  # [2 3 2]

# return_inverse:重建原数组
vals, inv = np.unique(a, return_inverse=True)
print(inv)         # [2 0 1 0 2 1 1]
print(vals[inv])   # [3 1 2 1 3 2 2] <- 还原成功

# bincount:整数频次统计(更快)
b = np.array([0, 1, 1, 2, 2, 2, 5])
print(np.bincount(b))  # [1 2 3 0 0 1]
# 索引即值,元素即计数

# bincount 加权
weights = np.array([1, 1, 2, 1, 1, 1, 3])
print(np.bincount(b, weights=weights))  # [1. 3. 3. 0. 0. 3.]
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

7. 综合示例:学生成绩多键排序

Alice 需要排出成绩榜:先按总分降序,总分相同按数学降序

PYTHON
import numpy as np

names   = np.array(['Alice', 'Bob', 'Charlie', 'Carol', 'David'])
math    = np.array([90, 85, 90, 85, 70])
english = np.array([80, 95, 75, 80, 90])
total   = math + english  # [170 180 165 165 160]

# lexsort:最后传入的键为主键(降序用负数)
idx = np.lexsort((-math, -total))
# -total 为主键降序,-math 为次键降序

print("排名  姓名    总分  数学  英语")
for rank, i in enumerate(idx, 1):
    print(f" {rank:>2}   {names[i]:<7} {total[i]:>3}   {math[i]:>2}   {english[i]:>2}")

# 输出:
# 排名  姓名    总分  数学  英语
#  1   Bob     180   85   95
#  2   Alice   170   90   80
#  3   Charlie 165   90   75   <- 总分 165,数学 90 排前面
#  4   Carol   165   85   80   <- 总分 165,数学 85 排后面
#  5   David   160   70   90

# 验证:Charlie 和 Carol 总分相同,Charlie 数学更高排前面
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

❓ 常见问题

Q sort 修改原数组吗?
A 不修改。np.sort() 返回副本;如果想原地排序,用 a.sort()(ndarray 方法,返回 None)。
Q argsort 返回什么?
A 返回一个索引数组,使得 a[argsort(a)] 等于 sort(a)。它不返回排序后的数据,而是告诉你“排好序后第 i 个位置对应原数组的第几个元素”。
Q lexsort 的键顺序怎么理解?
A np.lexsort((secondary, primary))——最后传入的键是主键。想象 SQL 的 ORDER BY primary, secondary,但参数顺序正好相反。
Q partition 和 sort 的区别?
A partition 只保证第 k 个位置放的是第 k 小的值,两侧不保证有序,复杂度 O(n);sort 全部有序,复杂度 O(n log n)。只需 Top-K 时用 partition 更快。
Q unique 怎么统计频次?
A vals, counts = np.unique(a, return_counts=True)。如果是非负整数数组,np.bincount(a) 更快,索引即值、元素即计数。
Q searchsorted 要求数组排序吗?
A 是的,searchsorted 基于二分查找,前提是输入数组已排序,否则结果无意义。

📖 小节


📝 作业

  1. 2D 按列排序:给定 2D 数组,分别按第 0 列升序和按第 1 列降序排列所有行。
PYTHON
data = np.array([[3, 7],
                 [1, 9],
                 [2, 5],
                 [1, 4]])
# 期望(按第 0 列升序):
# [[1 9]
#  [1 4]
#  [2 5]
#  [3 7]]
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
  1. argsort 同步排序:三个数组 nameagescore,用一次 argsortscore 降序同步重排三者。
PYTHON
name  = np.array(['Alice', 'Bob', 'Charlie', 'Carol'])
age   = np.array([20, 22, 21, 20])
score = np.array([85, 92, 78, 92])
# 期望输出:
# Bob   22  92
# Carol 20  92  <- 同分按原序
# Alice 20  85
# Charlie 21 78
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
  1. unique 词频统计:给定单词数组,用 np.unique 统计每个单词出现次数,并按频次降序输出。
PYTHON
words = np.array(['apple', 'banana', 'apple', 'cherry', 'banana', 'apple'])
# 期望:
# apple  : 3
# banana : 2
# cherry : 1
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏