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 流程图
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.partition 比 np.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 基于二分查找,前提是输入数组已排序,否则结果无意义。📖 小节
np.sort— 返回排序副本,不修改原数组np.argsort— 返回排序索引,灵魂操作np.lexsort— 多键排序,最后传入的键为主键np.partition— 部分排序,O(n) 取 Top-Knp.searchsorted— 二分查找插入位置,O(log n)np.unique— 去重 + 可选计数/逆映射np.bincount— 整数频次统计,比 unique 更快
📝 作业
- 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 版本、随机种子略有差异。
- argsort 同步排序:三个数组
name、age、score,用一次argsort按score降序同步重排三者。
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 版本、随机种子略有差异。
- 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 版本、随机种子略有差异。