1. 排序算法基础认知
排序算法是计算机科学中最基础也最重要的算法类别之一。作为程序员,我们几乎每天都要和各种排序需求打交道——从数据库查询优化到用户界面展示,从数据分析到算法设计,排序无处不在。理解不同排序算法的特性和适用场景,是每个开发者必备的核心能力。
我从业十多年来,处理过无数与排序相关的性能优化案例。最深刻的体会是:没有绝对"最好"的排序算法,只有最适合特定场景的选择。一个在小型数据集上表现优异的算法,可能在大数据量时完全不可用;一个理论时间复杂度优秀的算法,在实际硬件环境中可能因为缓存命中率低而表现不佳。
本文将用图解方式解析七大经典排序算法,包含:
- 基础排序三剑客:冒泡、选择、插入排序
- 高效排序双雄:快速排序与归并排序
- 特殊场景利器:堆排序与希尔排序
每种算法我都会给出:
- 核心思想图解
- 时间复杂度分析
- 空间复杂度对比
- 稳定性说明
- 适用场景建议
- 实际编码示例(Python实现)
- 常见实现陷阱
2. 基础排序算法详解
2.1 冒泡排序:最简单的排序方式
冒泡排序是大多数人学习的第一种排序算法。它的工作原理就像水中的气泡上浮——通过不断比较相邻元素并交换位置,将最大(或最小)的元素逐步"冒泡"到数列的一端。
算法步骤图解:
code复制初始序列:[5, 3, 8, 6, 2]
第一轮:
比较5和3 → 交换 → [3,5,8,6,2]
比较5和8 → 不交换
比较8和6 → 交换 → [3,5,6,8,2]
比较8和2 → 交换 → [3,5,6,2,8]
(最大值8已到位)
第二轮:
比较3和5 → 不交换
比较5和6 → 不交换
比较6和2 → 交换 → [3,5,2,6,8]
(次大值6已到位)
...依此类推直到完全有序
时间复杂度分析:
- 最优情况(已有序):O(n) —— 只需一轮比较
- 最差情况(逆序):O(n²) —— 需要n(n-1)/2次比较
- 平均情况:O(n²)
空间复杂度: O(1) —— 原地排序,只需常数级额外空间
稳定性: 稳定 —— 相等元素不会改变相对位置
适用场景:
- 教学演示算法原理
- 小规模数据(n<100)
- 基本有序的数据(接近最优情况)
Python实现要点:
python复制def bubble_sort(arr):
n = len(arr)
for i in range(n-1):
swapped = False # 优化:记录本轮是否发生交换
for j in range(n-1-i):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
swapped = True
if not swapped: # 提前终止
break
return arr
常见陷阱:
- 忘记设置提前终止条件(swapped标志)
- 内层循环边界错误(应为n-1-i而非n-1)
- 错误地使用双重循环都从0开始
2.2 选择排序:最直观的排序思想
选择排序的核心思想是:每次从未排序部分选择最小(或最大)元素,放到已排序部分的末尾。这种"选择-放置"的过程比冒泡排序的频繁交换更高效。
算法步骤图解:
code复制初始序列:[64, 25, 12, 22, 11]
第一轮:
遍历找到最小值11 → 与首位64交换 → [11,25,12,22,64]
第二轮:
从剩余部分找到最小值12 → 与25交换 → [11,12,25,22,64]
...依此类推直到完全有序
时间复杂度: 无论何种情况都是O(n²) —— 必须完整执行n(n-1)/2次比较
空间复杂度: O(1) —— 原地排序
稳定性: 不稳定 —— 交换可能改变相等元素的相对位置
适用场景:
- 对内存写入次数敏感的场景(比冒泡排序交换次数少)
- 需要简单实现的嵌入式系统
Python实现技巧:
python复制def selection_sort(arr):
for i in range(len(arr)):
min_idx = i
for j in range(i+1, len(arr)):
if arr[j] < arr[min_idx]:
min_idx = j
arr[i], arr[min_idx] = arr[min_idx], arr[i] # 单次交换
return arr
性能对比:
与冒泡排序相比,选择排序:
- 比较次数相同(都是O(n²))
- 交换次数更少(O(n)次 vs 冒泡的O(n²)次)
- 更适合写入成本高的场景(如Flash存储)
2.3 插入排序:小数据量的王者
插入排序的工作方式类似于整理扑克牌——将每个新元素插入到已排序部分的适当位置。对于小规模或基本有序的数据,它的表现往往优于更复杂的算法。
算法步骤图解:
code复制初始序列:[12,11,13,5,6]
第一步:12视为已排序,处理11 → 插入到12前 → [11,12,13,5,6]
第二步:处理13 → 保持位置 → [11,12,13,5,6]
第三步:处理5 → 插入到最前 → [5,11,12,13,6]
...依此类推直到完全有序
时间复杂度分析:
- 最优情况(已有序):O(n) —— 只需比较n-1次
- 最差情况(逆序):O(n²)
- 平均情况:O(n²)
空间复杂度: O(1)
稳定性: 稳定
适用场景:
- 小规模数据(n<50时通常最优)
- 基本有序的数据(如日志按时间近似有序)
- 作为快速排序的递归基(当子数组较小时切换)
优化实现:
python复制def insertion_sort(arr):
for i in range(1, len(arr)):
key = arr[i]
j = i-1
while j >=0 and key < arr[j]: # 边比较边移动
arr[j+1] = arr[j]
j -= 1
arr[j+1] = key # 插入正确位置
return arr
实际应用:
- Python内置的timsort算法在小数组时使用插入排序
- 数据库查询优化器对小结果集排序的选择
3. 高效排序算法解析
3.1 快速排序:分治思想的典范
快速排序是实际应用中最快的通用排序算法,由Tony Hoare于1959年发明。它采用分治策略——选取一个基准值(pivot),将数组分为小于和大于基准的两部分,然后递归排序子数组。
分治过程图解:
code复制初始序列:[10,80,30,90,40,50,70]
选择最后一个元素70作为pivot:
分区过程:
i维护小于pivot的边界,j扫描整个数组
最终分区结果:[10,30,40,50] 70 [80,90]
然后对两个子数组递归执行相同操作
时间复杂度分析:
- 最优情况(平衡划分):O(nlogn)
- 最差情况(极端不平衡):O(n²)
- 平均情况:O(nlogn)
空间复杂度:
- 最优/平均:O(logn) —— 递归栈深度
- 最差:O(n)
稳定性: 不稳定
关键优化点:
- pivot选择策略:
- 三数取中法(首、中、尾的中位数)
- 随机选择降低最差情况概率
- 小数组切换:当子数组小于某阈值(如10)时改用插入排序
- 尾递归优化:减少递归栈深度
Python实现:
python复制def quick_sort(arr):
if len(arr) <= 1:
return arr
pivot = arr[len(arr)//2] # 选择中间元素作为pivot
left = [x for x in arr if x < pivot]
middle = [x for x in arr if x == pivot]
right = [x for x in arr if x > pivot]
return quick_sort(left) + middle + quick_sort(right)
工程实践建议:
- 生产环境应使用原地分区版本减少内存使用
- 对于包含大量重复元素的数组,考虑三路分区优化
- 警惕递归深度过大导致的栈溢出(可改用迭代实现)
3.2 归并排序:稳定高效的典范
归并排序是分治法的另一个经典实现,由John von Neumann在1945年提出。它的核心思想是将数组分成两半,分别排序后再合并。
合并过程图解:
code复制初始数组:[38,27,43,3,9,82,10]
分割:
[38,27,43,3] 和 [9,82,10]
递归分割直到单元素,然后合并:
合并[27,38]和[3,43] → [3,27,38,43]
合并[9,82]和[10] → [9,10,82]
最终合并 → [3,9,10,27,38,43,82]
时间复杂度: 始终O(nlogn) —— 稳定高效
空间复杂度: O(n) —— 需要额外存储空间
稳定性: 稳定
适用场景:
- 需要稳定排序的场景
- 链表排序(归并排序是链表排序的最佳选择)
- 外部排序(数据量大于内存时)
Python实现:
python复制def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = merge_sort(arr[:mid])
right = merge_sort(arr[mid:])
return merge(left, right)
def merge(left, right):
result = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]: # 保持稳定性
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result.extend(left[i:])
result.extend(right[j:])
return result
性能对比:
与快速排序相比:
- 时间复杂度更稳定(没有最差情况)
- 需要额外O(n)空间
- 更适合处理大数据集和外部存储
4. 特殊场景排序算法
4.1 堆排序:原地排序的折中选择
堆排序结合了插入排序和归并排序的优点——像归并排序一样O(nlogn)时间复杂度,又像插入排序一样O(1)空间复杂度。它利用堆这种数据结构来实现排序。
堆构建过程:
code复制初始数组:[4,10,3,5,1]
构建最大堆:
10
/ \
5 3
/ \
4 1
排序过程:
交换根节点与末尾 → [1,5,3,4,10] (10已排序)
调整堆 → [5,4,3,1,10]
...依此类推
时间复杂度: 始终O(nlogn)
空间复杂度: O(1)
稳定性: 不稳定
适用场景:
- 需要原地排序且对最差时间复杂度有要求
- 需要同时获取前k个最大/最小元素
- 嵌入式系统等内存受限环境
Python实现:
python复制def heapify(arr, n, i):
largest = i
l = 2 * i + 1
r = 2 * i + 2
if l < n and arr[i] < arr[l]:
largest = l
if r < n and arr[largest] < arr[r]:
largest = r
if largest != i:
arr[i], arr[largest] = arr[largest], arr[i]
heapify(arr, n, largest)
def heap_sort(arr):
n = len(arr)
for i in range(n//2 - 1, -1, -1):
heapify(arr, n, i)
for i in range(n-1, 0, -1):
arr[i], arr[0] = arr[0], arr[i]
heapify(arr, i, 0)
return arr
实际应用:
- Linux内核的进程调度(使用优先级队列)
- 游戏开发中的场景渲染优先级排序
- 大数据处理中的Top K问题
4.2 希尔排序:插入排序的升级版
希尔排序是Donald Shell在1959年提出的改进版插入排序。它通过将数组分组并进行间隔排序,逐步减小间隔直到1(即标准插入排序),有效减少了元素移动次数。
间隔序列示例:
code复制初始数组:[8,3,1,2,7,5,6,4]
使用Knuth间隔序列:1,4,13,... (本例使用间隔4和1)
间隔4排序:
分组[8,7], [3,5], [1,6], [2,4] → 排序后[7,8], [3,5], [1,6], [2,4]
重组数组:[7,3,1,2,8,5,6,4]
间隔1排序(标准插入排序):
最终结果:[1,2,3,4,5,6,7,8]
时间复杂度:
- 取决于间隔序列选择
- 最佳已知序列可达到O(n(logn)^2)
- 平均情况优于O(n²)
空间复杂度: O(1)
稳定性: 不稳定
适用场景:
- 中等规模数据(数千到数万)
- 需要简单实现但比插入排序更高效
- 内存受限环境
Python实现:
python复制def shell_sort(arr):
n = len(arr)
gap = n // 2
while gap > 0:
for i in range(gap, n):
temp = arr[i]
j = i
while j >= gap and arr[j - gap] > temp:
arr[j] = arr[j - gap]
j -= gap
arr[j] = temp
gap //= 2
return arr
间隔序列选择:
- Hibbard序列:1,3,7,15,... (2^k-1) —— O(n^(3/2))
- Sedgewick序列:更复杂的组合 —— O(n^(4/3))
- Knuth序列:1,4,13,... (3^k-1)/2 —— O(n^(3/2))
5. 排序算法综合对比
5.1 性能特征对比表
| 算法 | 最优时间 | 平均时间 | 最差时间 | 空间 | 稳定 | 适用场景 |
|---|---|---|---|---|---|---|
| 冒泡 | O(n) | O(n²) | O(n²) | O(1) | 是 | 教学/小数据 |
| 选择 | O(n²) | O(n²) | O(n²) | O(1) | 否 | 少交换场景 |
| 插入 | O(n) | O(n²) | O(n²) | O(1) | 是 | 小/基本有序数据 |
| 快速 | O(nlogn) | O(nlogn) | O(n²) | O(logn) | 否 | 通用排序首选 |
| 归并 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 是 | 稳定排序/大数据 |
| 堆 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 否 | 原地排序/前K问题 |
| 希尔 | O(nlogn) | 取决于间隔 | O(n²) | O(1) | 否 | 中等规模数据 |
5.2 实际应用选择指南
选择排序算法的关键考量因素:
-
数据规模:
- n<50:插入排序
- 50<n<1000:希尔排序
- n>1000:快速/归并排序
-
内存限制:
- 严格受限:堆排序/希尔排序
- 充足:归并排序
-
稳定性要求:
- 必须稳定:归并排序
- 无要求:快速排序
-
数据特征:
- 基本有序:插入排序
- 大量重复:三路快速排序
- 范围有限:计数排序(非比较排序)
现代语言内置排序实现:
- Python:timsort(归并+插入排序混合)
- Java:Dual-Pivot QuickSort
- C++:Introsort(快速+堆排序混合)
5.3 算法可视化技巧
理解排序算法的最佳方式之一是观察它们的动态执行过程。以下是一些可视化方法:
- 控制台打印法:
python复制def bubble_sort_visual(arr):
n = len(arr)
for i in range(n-1):
print(f"Pass {i+1}: {arr}")
for j in range(n-1-i):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
print(f"Swap {arr[j]} and {arr[j+1]}: {arr}")
return arr
- 图形化工具:
- 使用matplotlib动态绘制柱状图
- 利用turtle模块制作排序动画
- 在线可视化工具如visualgo.net
- 性能对比实验:
python复制import time
import random
def test_sort(sort_func, size=1000):
arr = [random.randint(0, size) for _ in range(size)]
start = time.time()
sort_func(arr.copy())
return time.time() - start
sizes = [100, 1000, 10000]
for n in sizes:
t = test_sort(quick_sort, n)
print(f"QuickSort with {n} elements: {t:.5f}s")
6. 排序算法深度优化
6.1 快速排序工程实践
生产环境中的快速排序需要考虑许多教科书未涉及的现实因素:
优化策略:
- 小数组切换:
python复制def quick_sort_optimized(arr, threshold=10):
if len(arr) <= threshold:
return insertion_sort(arr)
# ...正常快速排序逻辑
- 三路分区(处理大量重复元素):
python复制def quick_sort_3way(arr):
if len(arr) <= 1:
return arr
pivot = arr[len(arr)//2]
less = [x for x in arr if x < pivot]
equal = [x for x in arr if x == pivot]
greater = [x for x in arr if x > pivot]
return quick_sort_3way(less) + equal + quick_sort_3way(greater)
- 尾递归优化:
python复制def quick_sort_tail(arr, low=0, high=None):
if high is None:
high = len(arr)-1
while low < high:
pi = partition(arr, low, high)
if pi - low < high - pi:
quick_sort_tail(arr, low, pi-1)
low = pi + 1
else:
quick_sort_tail(arr, pi+1, high)
high = pi - 1
return arr
6.2 归并排序的原地优化
传统归并排序需要O(n)额外空间,但可以通过复杂技巧实现原地合并:
原地合并技巧:
- 使用内部缓冲区
- 块交换旋转算法
- 基于二分查找的插入方法
示例实现:
python复制def merge_in_place(arr, start, mid, end):
i = start
j = mid + 1
while i <= mid and j <= end:
if arr[i] <= arr[j]:
i += 1
else:
temp = arr[j]
for k in range(j, i, -1):
arr[k] = arr[k-1]
arr[i] = temp
i += 1
mid += 1
j += 1
6.3 混合排序策略
现代排序库通常组合多种算法优势:
timsort核心思想:
- 扫描数组寻找自然升序/降序run
- 小run使用插入排序扩展至minrun长度
- 使用归并排序合并run,考虑缓存局部性
- 平衡合并顺序以控制内存使用
实现要点:
python复制def timsort(arr):
minrun = compute_minrun(len(arr))
runs = find_runs(arr, minrun)
while len(runs) > 1:
new_runs = []
for i in range(0, len(runs), 2):
if i+1 < len(runs):
merged = merge(runs[i], runs[i+1])
new_runs.append(merged)
else:
new_runs.append(runs[i])
runs = new_runs
return runs[0] if runs else []
7. 排序算法常见问题
7.1 算法选择困惑
Q:面对具体问题应该如何选择排序算法?
A:考虑以下决策树:
- 是否需要稳定排序?
- 是 → 归并排序
- 否 → 进入2
- 数据规模如何?
- n < 50 → 插入排序
- 50 ≤ n ≤ 1000 → 希尔排序
- n > 1000 → 进入3
- 内存是否受限?
- 是 → 堆排序
- 否 → 快速排序
- 数据是否有特殊特征?
- 基本有序 → 插入排序
- 大量重复 → 三路快速排序
- 范围有限 → 考虑非比较排序
7.2 实际性能不符合预期
Q:为什么快速排序在实际中比堆排序快?
理论时间复杂度相同,但实际差异来自:
- 缓存局部性:快速排序顺序访问内存,堆排序跳跃访问
- 常数因子:堆排序的每次操作更复杂
- 实现优化:快速排序有更多优化空间
实测数据对比(排序100万随机整数):
- 快速排序:0.45s
- 堆排序:1.20s
- 归并排序:0.85s
7.3 稳定性问题排查
Q:如何判断自定义排序是否稳定?
测试方法:
python复制def is_stable(sort_func):
data = [(2,'a'), (1,'b'), (2,'c'), (1,'d')]
sorted_data = sort_func(data)
return (sorted_data.index((2,'a')) < sorted_data.index((2,'c'))
and sorted_data.index((1,'b')) < sorted_data.index((1,'d')))
常见破坏稳定性的操作:
- 基于内存地址的比较
- 不保持相等元素的原始顺序
- 使用不稳定的中间操作(如某些语言的sort!方法)
7.4 内存使用异常
Q:为什么排序大数组时出现内存错误?
可能原因及解决方案:
- 递归深度过大(快速排序最差情况):
- 改用迭代实现
- 限制递归深度并切换堆排序
- 额外空间不足(归并排序):
- 使用原地归并版本
- 分批处理数据
- 系统栈溢出:
- 增大线程栈大小
- 改用非递归算法
7.5 多维度排序技巧
Q:如何实现多字段排序?
Python解决方案:
python复制from operator import itemgetter
data = [
{'name':'Alice','age':25,'score':88},
{'name':'Bob','age':25,'score':92},
{'name':'Charlie','age':30,'score':85}
]
# 按age升序,score降序
sorted_data = sorted(data, key=itemgetter('age', 'score'),
reverse=(False, True))
通用实现模式:
- 定义复合比较函数
- 先比较主键,相等时比较次键
- 注意不同字段的升降序需求
8. 排序算法扩展应用
8.1 非比较排序简介
当数据具有特殊性质时,可突破O(nlogn)限制:
计数排序:
- 要求:已知整数范围[0,k]
- 时间复杂度:O(n+k)
- 空间复杂度:O(k)
基数排序:
- 按数字位或字符逐轮排序
- 时间复杂度:O(d(n+k)) (d为位数)
- 常用于字符串排序
桶排序:
- 将数据分到有限数量的桶中
- 每个桶单独排序
- 时间复杂度取决于桶数量和桶内排序算法
8.2 并行排序策略
多线程快速排序:
- 分区后将两个子数组交给不同线程
- 注意负载均衡和线程创建开销
- 小数组切换为串行排序
MapReduce排序:
- Map阶段:将数据分片并局部排序
- Shuffle阶段:按key范围重新分配
- Reduce阶段:合并排序结果
GPU排序优化:
- 利用大规模并行处理能力
- 适合bitonic sort等并行友好算法
- 需要处理内存访问模式优化
8.3 外部排序技术
当数据无法全部装入内存时:
多路归并排序:
- 将数据分成若干能装入内存的块
- 每块单独排序后写回磁盘
- 使用优先队列进行多路归并
优化技巧:
- 增加归并路数减少IO次数
- 使用缓冲区减少磁盘访问
- 考虑磁盘的寻道时间优化
实际应用:
- 数据库大规模排序
- 大数据处理框架(如Spark)
- 日志分析系统
8.4 现代硬件优化
缓存优化策略:
- 分块处理以适应缓存行
- 预取数据减少缓存缺失
- 调整访问模式提高局部性
SIMD指令利用:
- 使用AVX/SSE指令并行比较
- 向量化交换操作
- 需要特定硬件支持
NUMA架构考量:
- 数据分区对应内存节点
- 减少跨节点访问
- 线程绑定到特定CPU
9. 排序算法实战案例
9.1 海量数据Top K问题
问题描述: 从10亿个整数中找出前100大的数
解决方案:
- 维护大小为100的最小堆
- 遍历数据,比堆顶大则替换并调整堆
- 最终堆中即为Top 100
复杂度分析:
- 时间复杂度:O(nlogk) (k=100)
- 空间复杂度:O(k)
Python实现:
python复制import heapq
def top_k(arr, k):
min_heap = []
for num in arr:
if len(min_heap) < k:
heapq.heappush(min_heap, num)
elif num > min_heap[0]:
heapq.heapreplace(min_heap, num)
return sorted(min_heap, reverse=True)
9.2 区间合并问题
问题描述: 合并所有重叠区间
示例:
输入:[[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解决步骤:
- 按区间起点排序(O(nlogn))
- 遍历并合并重叠区间(O(n))
关键实现:
python复制def merge_intervals(intervals):
if not intervals:
return []
intervals.sort(key=lambda x: x[0])
merged = [intervals[0]]
for current in intervals[1:]:
last = merged[-1]
if current[0] <= last[1]:
last[1] = max(last[1], current[1])
else:
merged.append(current)
return merged
9.3 任务调度问题
问题描述: 安排会议室使用,使举行的会议数最多
贪心算法:
- 按结束时间排序
- 每次选择结束最早的可用会议
- 排除与之冲突的会议
实现代码:
python复制def max_meetings(start, end):
meetings = sorted(zip(start, end), key=lambda x: x[1])
count = 0
last_end = -1
for s, e in meetings:
if s >= last_end:
count += 1
last_end = e
return count
9.4 逆序对计数
问题描述: 计算数组中逆序对的数量(i<j且arr[i]>arr[j])
归并排序解法:
在合并过程中统计跨子数组的逆序对
实现示例:
python复制def count_inversions(arr):
if len(arr) <= 1:
return arr, 0
mid = len(arr) // 2
left, inv_left = count_inversions(arr[:mid])
right, inv_right = count_inversions(arr[mid:])
merged, inv_merge = merge_and_count(left, right)
total = inv_left + inv_right + inv_merge
return merged, total
def merge_and_count(left, right):
result = []
i = j = 0
inv_count = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
inv_count += len(left) - i
result.extend(left[i:])
result.extend(right[j:])
return result, inv_count
10. 排序算法进阶话题
10.1 自适应排序研究
定义: 算法时间复杂度随输入有序程度变化的特性
典型算法:
- 插入排序:对基本有序数据接近O(n)
- 冒泡排序:优化版本可检测有序提前终止
- Timsort:利用自然run的已有顺序
度量指标:
- 逆序对数量
- 上升序列数量
- 局部有序程度
10.2 比较排序下限证明
决策树模型:
- 每次比较产生两个分支
- n个元素有n!种排列
- 需要至少⌈log₂(n!)⌉次比较
斯特林公式近似:
log₂(n!) ≈ nlog₂n - nlog₂e
结论: 比较排序最差情况下至少需要Ω(nlogn)次比较
10.3 并行排序理论
工作深度(Work Depth)模型:
- 工作:总操作量
- 深度:关键路径长度
- 理想加速比:工作/深度
典型算法分析:
- 并行归并排序:O(n)工作,O(logn)深度
- 样本排序:O(nlogn)工作,O(log²n)深度
- 双调排序:O(nlog²n)工作,O(log²n)深度
10.4 量子排序展望
量子比较器:
- 可同时比较多个元素
- 实现超位置换
量子排序算法:
- 复杂度可突破经典下限
- 当前局限:量子比特稳定性
- 实际应用仍处研究阶段
潜在优势领域:
- 超大规模数据排序
- 高维数据排序
- 加密数据排序