算法思路:
依次比较相邻元素,若顺序错误则交换,每一轮将最大的元素“冒泡”到末尾。重复直到所有元素有序。
代码实现:
def bubble_sort(arr):
n = len(arr)
for i in range(n):
# 提前退出标志位(若某轮未交换,说明已有序)
swapped = False
for j in range(0, n-i-1):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
swapped = True
if not swapped:
break
return arr
注意:
时间复杂度:平均和最坏 O(n²),最好 O(n)(已有序时)。
适用场景:教学用途,小规模数据或基本有序数据。
算法思路:
每一轮找到未排序部分的最小值,将其与未排序部分的第一个元素交换位置。
代码实现:
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]
return arr
注释:
时间复杂度:始终 O(n²)(无论数据是否有序)。
适用场景:教学或简单场景,不推荐实际应用。
算法思路:
将未排序元素逐个插入到已排序部分的正确位置,类似整理扑克牌。
代码实现:
def insertion_sort(arr):
for i in range(1, len(arr)):
key = arr[i] # 当前待插入元素
j = i-1
# 将比 key 大的元素后移
while j >=0 and key < arr[j]:
arr[j+1] = arr[j]
j -= 1
arr[j+1] = key
return arr
注释:
时间复杂度:平均和最坏 O(n²),最好 O(n)(已有序时)。
适用场景:小规模数据或基本有序数据,实际应用中的优化(如TimSort的组成部分)。
算法思路:
分治法策略。选择一个基准元素,将数组分为“小于基准”和“大于基准”两部分,递归排序子数组。
代码实现:
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)
# 更高效的原地分区版本(Hoare分区法)
def partition(arr, low, high):
pivot = arr[high]
i = low - 1
for j in range(low, high):
if arr[j] <= pivot:
i += 1
arr[i], arr[j] = arr[j], arr[i]
arr[i+1], arr[high] = arr[high], arr[i+1]
return i+1
def quick_sort_inplace(arr, low=0, high=None):
if high is None:
high = len(arr) - 1
if low < high:
pi = partition(arr, low, high)
quick_sort_inplace(arr, low, pi-1)
quick_sort_inplace(arr, pi+1, high)
return arr
注释:
时间复杂度:平均 O(n log n),最坏 O(n²)(当基准选择极不平衡时)。
适用场景:大规模数据,实际应用广泛(需优化基准选择)。
算法思路:
分治法策略。将数组分为两半,递归排序后合并两个有序子数组。
代码实现:
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):
merged = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
merged.append(left[i])
i += 1
else:
merged.append(right[j])
j += 1
merged.extend(left[i:])
merged.extend(right[j:])
return merged
注释:
时间复杂度:始终 O(n log n),稳定但需要额外空间。
适用场景:需要稳定排序或链表排序。
算法思路:
将数组构建为大顶堆,每次取出堆顶元素(最大值),调整剩余堆结构。
代码实现:
def heapify(arr, n, i):
largest = i
left = 2 * i + 1
right = 2 * i + 2
if left < n and arr[left] > arr[largest]:
largest = left
if right < n and arr[right] > arr[largest]:
largest = right
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
注释:
时间复杂度:始终 O(n log n),原地排序但不稳定。
适用场景:需要原地排序且不要求稳定性的场景。
算法思路:
希尔排序是插入排序的优化版本,通过将数组分成多个子序列(按固定间隔 gap
)进行插入排序,逐步缩小间隔直到 gap=1
,最终对整个数组进行一次插入排序。通过这种方式减少元素的移动次数。
代码实现:
def shell_sort(arr):
n = len(arr)
gap = n // 2 # 初始间隔设为数组长度的一半(Knuth序列可优化为3h+1)
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 = gap // 2 # 缩小间隔
return arr
注释:
时间复杂度:取决于间隔序列,平均 O(n log n) ~ O(n^(3/2)),最坏 O(n²)。
空间复杂度:O(1),原地排序。
稳定性:不稳定(子序列插入可能破坏顺序)。
适用场景:中等规模数据,插入排序的优化替代方案。
算法思路:
非比较排序,适用于整数且范围已知的情况。统计每个元素的出现次数,累加计数数组以确定元素的最终位置,最后反向填充结果数组。
代码实现:
def counting_sort(arr):
if not arr:
return []
max_val = max(arr)
min_val = min(arr)
range_size = max_val - min_val + 1
# 初始化计数数组和结果数组
count = [0] * range_size
output = [0] * len(arr)
# 统计元素出现次数
for num in arr:
count[num - min_val] += 1
# 累加计数数组以确定位置
for i in range(1, range_size):
count[i] += count[i-1]
# 反向填充结果数组(保证稳定性)
for num in reversed(arr):
output[count[num - min_val] - 1] = num
count[num - min_val] -= 1
return output
注释:
时间复杂度:O(n + k),其中 k
是数据范围(max-min)。
空间复杂度:O(n + k),需要额外空间。
稳定性:稳定(反向填充保证相同元素的顺序)。
适用场景:整数数据且范围较小(如年龄、分数等)。
算法思路:
将数据分到多个“桶”中,每个桶单独排序(通常用插入排序),最后合并所有桶。假设数据均匀分布,桶的数量通常与输入规模相关。
代码实现:
def bucket_sort(arr, bucket_size=5):
if len(arr) == 0:
return arr
min_val = min(arr)
max_val = max(arr)
# 计算桶的数量
bucket_count = (max_val - min_val) // bucket_size + 1
buckets = [[] for _ in range(bucket_count)]
# 将元素分配到桶中
for num in arr:
idx = (num - min_val) // bucket_size
buckets[idx].append(num)
# 对每个桶进行排序(这里用插入排序)
sorted_arr = []
for bucket in buckets:
# 插入排序(或其他简单排序)
for i in range(1, len(bucket)):
key = bucket[i]
j = i-1
while j >=0 and bucket[j] > key:
bucket[j+1] = bucket[j]
j -= 1
bucket[j+1] = key
sorted_arr.extend(bucket)
return sorted_arr
注释:
时间复杂度:平均 O(n + k),最坏 O(n²)(当所有元素集中在一个桶)。
空间复杂度:O(n + k),需要额外桶空间。
稳定性:稳定(取决于桶内排序算法的稳定性)。
适用场景:数据均匀分布的浮点数(如0~1的小数)。
算法思路:
按位排序,从最低位到最高位依次对每一位使用稳定的排序算法(如计数排序)。适用于整数或定长字符串。
代码实现:
def radix_sort(arr):
max_val = max(arr) if arr else 0
exp = 1 # 从个位开始
while max_val // exp > 0:
# 使用计数排序对当前位排序
counting = [0] * 10 # 0-9的基数
output = [0] * len(arr)
# 统计当前位的出现次数
for num in arr:
digit = (num // exp) % 10
counting[digit] += 1
# 累加计数数组
for i in range(1, 10):
counting[i] += counting[i-1]
# 反向填充保证稳定性
for num in reversed(arr):
digit = (num // exp) % 10
output[counting[digit] - 1] = num
counting[digit] -= 1
arr = output
exp *= 10 # 处理更高位
return arr
注释:
时间复杂度:O(d(n + k)),d
是最大位数,k
是基数范围(如10进制则k=10)。
空间复杂度:O(n + k)。
稳定性:稳定(依赖底层计数排序的稳定性)。
适用场景:整数或定长字符串(如手机号、身份证号)。
排序算法 | 平均时间复杂度 | 最坏时间复杂度 | 最好时间复杂度 | 空间复杂度 | 原地排序 | 稳定性 | 适用场景 |
---|---|---|---|---|---|---|---|
冒泡排序 | 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(n log n) | O(n²) | O(n log n) | O(log n) | 是 | 不稳定 | 大规模数据、实际应用(需优化基准选择) |
归并排序 | O(n log n) | O(n log n) | O(n log n) | O(n) | 否 | 稳定 | 稳定排序、链表排序、外部排序 |
堆排序 | O(n log n) | O(n log n) | O(n log n) | O(1) | 是 | 不稳定 | 原地排序且无需额外空间 |
希尔排序 | O(n log n)~O(n²) | O(n²) | O(n log n) | O(1) | 是 | 不稳定 | 中等规模数据、插入排序优化 |
计数排序 | O(n + k) | O(n + k) | O(n + k) | O(n + k) | 否 | 稳定 | 整数且范围较小的数据(如年龄、分数) |
桶排序 | O(n + k) | O(n²) | O(n + k) | O(n + k) | 否 | 稳定 | 均匀分布的浮点数 |
基数排序 | O(d(n + k)) | O(d(n + k)) | O(d(n + k)) | O(n + k) | 否 | 稳定 | 整数或定长字符串(如身份证号) |
时间复杂度:
k:数据范围(如计数排序中的 max - min
)或基数排序的基数(如10进制为10)。
d:基数排序中数字的最大位数。
快速排序的最坏时间复杂度发生在基准选择极不平衡时(如已有序数组且基准固定为第一个元素)。
原地性:
原地排序指不需要额外空间(或仅需常数空间),直接在原数组上操作。
归并排序、计数排序、桶排序、基数排序需要额外空间,因此非原地。
稳定性:
稳定排序保证相等元素的相对顺序不变。
快速排序和堆排序的交换逻辑可能破坏稳定性。
适用场景优先级:
小规模数据:冒泡、选择、插入排序。
大规模数据:快速排序、归并排序、堆排序。
特定数据分布:计数、桶、基数排序。