在后端开发中,数据排序无处不在。从数据库查询结果的排序,到消息队列中消息的优先级处理,再到前端页面数据的展示,排序算法都是核心组成部分。如果排序算法选择不当,或者实现效率不高,轻则影响用户体验,重则导致系统性能瓶颈。本文将深入剖析常见的七大排序算法的基本原理,并结合实际场景进行分析,助你选择最合适的排序算法。
常见的性能问题包括:
- 大数据量排序效率低:当数据量达到百万甚至千万级别时,某些排序算法的耗时会呈指数级增长。
- 频繁排序导致 CPU 占用过高:例如,实时排行榜需要频繁更新排序,如果算法效率不高,会导致 CPU 占用率飙升,影响服务器稳定性。
- 内存占用过多:某些排序算法需要额外的内存空间,当数据量很大时,内存占用会成为瓶颈。
七大排序算法详解
1. 冒泡排序(Bubble Sort)
冒泡排序是最简单的排序算法之一。它重复地遍历要排序的列表,比较每对相邻的项目,并且如果它们顺序错误就交换它们。重复对列表进行排序的步骤,直到没有相邻的元素需要交换为止,这意味着该列表已排序。
原理:从头开始,比较相邻的元素。如果第一个比第二个大,就交换他们两个。对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。针对所有的元素重复以上的步骤,除了最后一个。持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
def bubble_sort(arr):
n = len(arr)
for i in range(n):
for j in range(0, n-i-1):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j] # 交换元素
2. 选择排序(Selection Sort)
选择排序是一种简单直观的排序算法。它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(或最大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。
原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
def selection_sort(arr):
n = len(arr)
for i in range(n):
min_idx = i
for j in range(i+1, n):
if arr[j] < arr[min_idx]:
min_idx = j
arr[i], arr[min_idx] = arr[min_idx], arr[i] # 交换元素
3. 插入排序(Insertion Sort)
插入排序的工作方式是,构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
原理:从第一个元素开始,该元素可以认为已经被排序。取出下一个元素,在已经排序的元素序列中从后向前扫描。如果该元素(已排序)大于新元素,将该元素移到下一位置。重复步骤3,直到找到已排序的元素小于或者等于新元素的位置。将新元素插入到该位置后。重复步骤2~5。
def insertion_sort(arr):
n = len(arr)
for i in range(1, n):
key = arr[i]
j = i-1
while j >= 0 and key < arr[j] :
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = key
4. 希尔排序(Shell Sort)
希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。希尔排序是非稳定排序算法。
原理:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体做法:选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;按增量序列个数k,对序列进行k 趟排序;每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
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
5. 归并排序(Merge Sort)
归并排序是一种基于分治策略的排序算法。它的基本思想是将待排序序列递归地分成两个子序列,分别对子序列进行排序,然后将排序好的子序列合并成一个有序序列。
原理:将长度为n的输入序列分成两个长度为n/2的子序列;对这两个子序列分别采用归并排序;将两个排序好的子序列合并成一个最终的排序序列。
def merge_sort(arr):
if len(arr) > 1:
mid = len(arr)//2
L = arr[:mid]
R = arr[mid:]
merge_sort(L)
merge_sort(R)
i = j = k = 0
while i < len(L) and j < len(R):
if L[i] < R[j]:
arr[k] = L[i]
i += 1
else:
arr[k] = R[j]
j += 1
k += 1
while i < len(L):
arr[k] = L[i]
i += 1
k += 1
while j < len(R):
arr[k] = R[j]
j += 1
k += 1
6. 快速排序(Quick Sort)
快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为较小和较大的2个子序列,然后递归地排序两个子序列。
原理:从数列中挑出一个元素,称为 "基准"(pivot)。重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
def quick_sort(arr):
if len(arr) <= 1:
return arr
pivot = arr[len(arr) // 2]
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)
7. 堆排序(Heap Sort)
堆排序是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
原理:将待排序的序列构建成一个大顶堆或小顶堆。此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值元素。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。
def heapify(arr, n, i):
largest = i # Initialize largest as root
l = 2 * i + 1 # left = 2*i + 1
r = 2 * i + 2 # right = 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] # swap
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] # swap
heapify(arr, i, 0)
实战避坑经验
- 选择合适的排序算法:根据数据规模、数据特征、以及性能要求选择最合适的算法。例如,对于小规模数据,插入排序可能比快速排序更快。对于大规模数据,归并排序和堆排序是不错的选择。
- 避免频繁排序:对于实时性要求不高的场景,可以考虑批量排序,减少 CPU 占用。
- 优化代码实现:即使选择了合适的算法,代码实现也很重要。例如,可以利用 Python 的内置函数
sorted()进行排序,底层实现经过高度优化,性能通常比自己实现的排序算法更好。 - 内存优化:尽量选择原地排序算法(in-place sorting),减少内存占用。如果必须使用非原地排序算法,可以考虑分批排序,减少单次排序的数据量。
- 并发排序:对于 CPU 密集型的排序任务,可以考虑使用多线程或多进程进行并发排序,提高排序效率。例如,可以使用 Python 的
concurrent.futures模块实现并发排序。当然,在实现并发排序时,需要注意线程安全问题,避免数据竞争。
在实际项目中,例如搭建使用 Nginx 反向代理的电商网站,在高并发场景下,如果需要对商品进行排序展示,需要选择合适的排序算法。如果使用宝塔面板管理服务器,可以监控 CPU 和内存使用情况,从而判断排序算法是否成为性能瓶颈。如果发现排序算法效率不高,可以考虑使用更高效的算法,或者对代码进行优化。同时,还需要考虑使用缓存技术,减少数据库查询的压力,提高网站的响应速度。例如,可以使用 Redis 缓存排序结果,避免每次都进行排序操作。在使用 Nginx 进行负载均衡时,也可以根据用户的地理位置或访问频率,将请求分发到不同的服务器,从而提高系统的整体性能。上述提到的七大排序算法,各有其特点和应用场景,需要结合实际情况进行选择。
冠军资讯
键盘上的咸鱼