在后端开发中,常见排序算法是基础也是关键。数据排序在各种业务场景中都扮演着重要角色,比如用户列表排序、商品价格排序、搜索结果排序等。选择合适的排序算法,直接影响到系统的性能和用户体验。本文将深入探讨几种常见的排序算法,并结合实际案例进行分析,让你在实际开发中能够游刃有余。
排序算法概览
我们将讨论以下几种常见的排序算法:
- 冒泡排序
- 选择排序
- 插入排序
- 快速排序
- 归并排序
- 堆排序
- 计数排序
- 桶排序
- 基数排序
对于每种算法,我们将介绍其原理、时间复杂度、空间复杂度,并给出示例代码。
冒泡排序
冒泡排序是最简单的排序算法之一。它重复地遍历要排序的列表,比较相邻的元素,如果它们的顺序错误就交换它们。重复这个过程直到没有需要交换的元素。
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]
- 时间复杂度: O(n^2)
- 空间复杂度: O(1)
- 稳定性: 稳定
选择排序
选择排序的工作原理是每次从待排序的数据元素中选出最小(或最大)的一个元素存放在序列的起始位置,然后再从剩余未排序元素中继续寻找最小(或最大)元素,然后放到已排序序列的末尾。以此类推,直到全部待排序的数据元素排完。
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]
- 时间复杂度: O(n^2)
- 空间复杂度: O(1)
- 稳定性: 不稳定
插入排序
插入排序的工作方式是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
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
- 时间复杂度: O(n^2)
- 空间复杂度: O(1)
- 稳定性: 稳定
快速排序
快速排序是一种分而治之的算法。它选择一个元素作为“基准”(pivot),并将列表分成两个子列表:小于基准的元素和大于基准的元素。然后递归地对子列表进行排序。
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)
- 时间复杂度: 平均 O(n log n),最坏 O(n^2)
- 空间复杂度: O(log n)
- 稳定性: 不稳定
实战避坑: 快速排序在处理已经基本有序的数据时,性能会退化到 O(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
- 时间复杂度: O(n log n)
- 空间复杂度: O(n)
- 稳定性: 稳定
应用场景: 归并排序非常适合处理大规模数据,例如外部排序。在分布式系统中,可以利用 MapReduce 框架并行地进行归并排序,提高排序效率。可以配合 Nginx 使用,例如对日志文件进行排序,从而方便后续的数据分析。
堆排序
堆排序利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
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)
- 时间复杂度: O(n log n)
- 空间复杂度: O(1)
- 稳定性: 不稳定
计数排序
计数排序是一种非基于比较的排序算法,它适用于一定范围内的整数排序。其核心思想是统计每个整数出现的次数,然后根据统计结果将整数重新排列。
def counting_sort(arr):
max_element = int(max(arr))
count_array = [0] * (max_element + 1)
for element in arr:
count_array[element] += 1
sorted_array = []
for i in range(len(count_array)):
sorted_array.extend([i] * count_array[i])
return sorted_array
- 时间复杂度: O(n+k),其中k是整数的范围
- 空间复杂度: O(k)
- 稳定性: 稳定
限制: 计数排序只适用于整数排序,且整数的范围不宜过大,否则会占用大量内存。
桶排序
桶排序是将数组分到有限数量的桶子里。每个桶子再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序)。
def bucket_sort(arr):
bucket = []
for i in range(len(arr)):
bucket.append([])
for j in arr:
index_b = int(10 * j)
bucket[index_b].append(j)
for i in range(len(arr)):
bucket[i] = sorted(bucket[i])
k = 0
for i in range(len(arr)):
for j in range(len(bucket[i])):
arr[k] = bucket[i][j]
k += 1
return arr
- 时间复杂度: O(n+k),其中k是桶的数量
- 空间复杂度: O(n+k)
- 稳定性: 稳定
基数排序
基数排序是一种非比较型的整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。基数排序可以采用LSD(Least Significant Digit)或MSD(Most Significant Digit)两种方法进行排序。
def radix_sort(arr):
max_value = max(arr)
radix = 1
while max_value // radix > 0:
arr = counting_sort_for_radix(arr, radix)
radix *= 10
return arr
def counting_sort_for_radix(arr, radix):
size = len(arr)
output = [0] * size
count = [0] * 10
for i in range(size):
index = arr[i] // radix
count[index % 10] += 1
for i in range(1, 10):
count[i] += count[i - 1]
i = size - 1
while i >= 0:
index = arr[i] // radix
output[count[index % 10] - 1] = arr[i]
count[index % 10] -= 1
i -= 1
return output
- 时间复杂度: O(nk),其中n是数组长度,k是最大值的位数
- 空间复杂度: O(n+k)
- 稳定性: 稳定
总结
选择合适的常见排序算法取决于具体的应用场景。如果数据量较小,可以选择简单的冒泡排序或插入排序。如果对性能要求较高,则可以选择快速排序或归并排序。对于特定类型的数据,例如整数,可以选择计数排序或基数排序。理解各种排序算法的原理和特性,才能在实际开发中做出正确的选择,提高系统的性能和效率。
冠军资讯
程序员老猫