七种排序算法比较与选择[Python ]
-
-
- [1. 冒泡排序(Bubble Sort)](#1. 冒泡排序(Bubble Sort))
- [2. 选择排序(Selection Sort)](#2. 选择排序(Selection Sort))
- [3. 插入排序(Insertion Sort)](#3. 插入排序(Insertion Sort))
- [4. 希尔排序(Shell Sort)](#4. 希尔排序(Shell Sort))
- [5. 快速排序(Quick Sort)](#5. 快速排序(Quick Sort))
- [6. 归并排序(Merge Sort)](#6. 归并排序(Merge Sort))
- [7. 堆排序(Heap Sort)](#7. 堆排序(Heap Sort))
- 总结
-
1. 冒泡排序(Bubble Sort)
原理 :重复遍历列表,每次比较相邻元素,若顺序错误则交换,直到没有元素需要交换(大元素像气泡一样"浮"到末尾)。
时间复杂度 :O(n²)(最坏/平均),O(n)(最好,已排序时)
空间复杂度:O(1)(原地排序)
python
def bubble_sort(arr):
# 获取列表长度,n为元素个数
n = len(arr)
# 外层循环:需要进行n-1轮比较(每轮确定一个最大元素的位置)
for i in range(n - 1):
# 标记本轮是否发生交换,初始为False(假设已排序)
swapped = False
# 内层循环:每轮比较到第n-1-i个元素(因为后面i个已排好序)
for j in range(0, 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
# 测试
print(bubble_sort([64, 34, 25, 12, 22, 11, 90])) # 输出:[11, 12, 22, 25, 34, 64, 90]
2. 选择排序(Selection Sort)
原理 :每次从待排序部分找到最小(大)元素,放到已排序部分的末尾,直到全部排完。
时间复杂度 :O(n²)(所有情况)
空间复杂度:O(1)(原地排序)
python
def selection_sort(arr):
# 获取列表长度
n = len(arr)
# 外层循环:遍历到倒数第二个元素(最后一个自然有序)
for i in range(n - 1):
# 记录待排序部分中最小元素的索引,初始假设i位置为最小
min_index = i
# 内层循环:从i+1开始找比当前min_index更小的元素
for j in range(i + 1, n):
if arr[j] < arr[min_index]:
# 更新最小元素索引
min_index = j
# 将找到的最小元素与i位置元素交换(放入已排序部分末尾)
arr[i], arr[min_index] = arr[min_index], arr[i]
return arr
# 测试
print(selection_sort([64, 34, 25, 12, 22, 11, 90])) # 输出:[11, 12, 22, 25, 34, 64, 90]
3. 插入排序(Insertion Sort)
原理 :将列表分为"已排序"和"待排序"两部分,每次从待排序部分取一个元素,插入到已排序部分的正确位置(类似整理扑克牌)。
时间复杂度 :O(n²)(最坏/平均),O(n)(最好,已排序时)
空间复杂度:O(1)(原地排序)
python
def insertion_sort(arr):
# 获取列表长度
n = len(arr)
# 外层循环:从第二个元素开始(第一个元素默认已排序)
for i in range(1, n):
# 记录待插入的元素(当前元素)
key = arr[i]
# 内层循环:从已排序部分的末尾(i-1)向前比较
j = i - 1
# 若已排序元素大于key,将其向后移动一位
while j >= 0 and key < arr[j]:
arr[j + 1] = arr[j]
j -= 1
# 将key插入到正确位置(j+1是插入索引)
arr[j + 1] = key
return arr
# 测试
print(insertion_sort([64, 34, 25, 12, 22, 11, 90])) # 输出:[11, 12, 22, 25, 34, 64, 90]
4. 希尔排序(Shell Sort)
原理 :插入排序的改进版,通过"步长"将列表分组,对每组进行插入排序,逐步减小步长至1(最终退化为插入排序)。
时间复杂度 :O(n log n) ~ O(n²)(取决于步长选择)
空间复杂度:O(1)(原地排序)
python
def shell_sort(arr):
# 获取列表长度
n = len(arr)
# 初始化步长(通常从n//2开始,逐步减半)
gap = n // 2
# 当步长大于0时,持续分组排序
while gap > 0:
# 对每个分组进行插入排序(从gap位置开始遍历)
for i in range(gap, n):
# 记录当前分组中待插入的元素
temp = arr[i]
# 分组内的插入排序:与组内前一个元素比较
j = i
# 若组内前一个元素(j-gap)大于temp,向后移动
while j >= gap and arr[j - gap] > temp:
arr[j] = arr[j - gap]
j -= gap
# 将temp插入到分组内的正确位置
arr[j] = temp
# 步长减半(逐渐缩小分组规模)
gap //= 2
return arr
# 测试
print(shell_sort([64, 34, 25, 12, 22, 11, 90])) # 输出:[11, 12, 22, 25, 34, 64, 90]
5. 快速排序(Quick Sort)
原理 :分治法思想,选择一个"基准"元素,将列表分为"小于基准"和"大于基准"两部分,递归排序两部分。
时间复杂度 :O(n log n)(平均),O(n²)(最坏,如已排序时)
空间复杂度:O(log n) ~ O(n)(递归栈开销)
python
def quick_sort(arr):
# 辅助函数:实现分区操作(核心)
def partition(low, high):
# 选择最右侧元素作为基准
pivot = arr[high]
# i是"小于基准"区域的边界索引(初始为low-1)
i = low - 1
# 遍历low到high-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(low, high):
# 递归终止条件:low >= high时子列表长度为0或1
if low < high:
# 分区,获取基准元素的索引
pi = partition(low, high)
# 递归排序基准左侧子列表
_quick_sort(low, pi - 1)
# 递归排序基准右侧子列表
_quick_sort(pi + 1, high)
# 初始化递归(从0到n-1)
_quick_sort(0, len(arr) - 1)
return arr
# 测试
print(quick_sort([64, 34, 25, 12, 22, 11, 90])) # 输出:[11, 12, 22, 25, 34, 64, 90]
6. 归并排序(Merge Sort)
原理 :分治法思想,将列表递归拆分为两个子列表,排序后合并为一个有序列表(核心是"合并"两个有序子列表)。
时间复杂度 :O(n log n)(所有情况)
空间复杂度:O(n)(需要额外空间存储临时列表)
python
def merge_sort(arr):
# 递归终止条件:列表长度小于等于1时无需排序
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:left的索引,j:right的索引(初始都为0)
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
# 将left中剩余元素加入结果(若有)
result.extend(left[i:])
# 将right中剩余元素加入结果(若有)
result.extend(right[j:])
return result
# 测试
print(merge_sort([64, 34, 25, 12, 22, 11, 90])) # 输出:[11, 12, 22, 25, 34, 64, 90]
7. 堆排序(Heap Sort)
原理 :利用"堆"(完全二叉树)的特性,先构建大顶堆(父节点 >= 子节点),再反复将堆顶(最大值)与末尾元素交换,调整剩余元素为大顶堆。
时间复杂度 :O(n log n)(所有情况)
空间复杂度:O(1)(原地排序)
python
def heap_sort(arr):
n = len(arr)
# 构建大顶堆的辅助函数(调整以i为根的子树为大顶堆)
def heapify(i, heap_size):
# 初始化最大值索引为当前根节点i
largest = i
# 左子节点索引(2*i + 1)
left = 2 * i + 1
# 右子节点索引(2*i + 2)
right = 2 * i + 2
# 若左子节点存在且大于当前最大值,更新最大值索引
if left < heap_size and arr[left] > arr[largest]:
largest = left
# 若右子节点存在且大于当前最大值,更新最大值索引
if right < heap_size and arr[right] > arr[largest]:
largest = right
# 若最大值不是根节点,交换并递归调整子树
if largest != i:
arr[i], arr[largest] = arr[largest], arr[i]
heapify(largest, heap_size)
# 1. 构建初始大顶堆(从最后一个非叶子节点开始向前调整)
for i in range(n // 2 - 1, -1, -1):
heapify(i, n)
# 2. 逐步提取堆顶元素(最大值)并调整堆
for i in range(n - 1, 0, -1):
# 将堆顶(最大值)与当前未排序部分的末尾元素交换
arr[0], arr[i] = arr[i], arr[0]
# 调整剩余元素为大顶堆(堆大小减1)
heapify(0, i)
return arr
# 测试
print(heap_sort([64, 34, 25, 12, 22, 11, 90])) # 输出:[11, 12, 22, 25, 34, 64, 90]
总结
算法 | 时间复杂度(平均) | 空间复杂度 | 稳定性 | 特点 |
---|---|---|---|---|
冒泡排序 | O(n²) | O(1) | 稳定 | 简单,适合小规模数据 |
选择排序 | O(n²) | O(1) | 不稳定 | 交换次数少 |
插入排序 | O(n²) | O(1) | 稳定 | 适合接近有序的数据 |
希尔排序 | O(n log n) | O(1) | 不稳定 | 插入排序的高效改进版 |
快速排序 | O(n log n) | O(log n) | 不稳定 | 实际应用中最快 |
归并排序 | O(n log n) | O(n) | 稳定 | 适合大规模数据,可并行 |
堆排序 | O(n log n) | O(1) | 不稳定 | 利用堆结构,无需额外空间 |
根据场景选择合适的算法:小规模数据用简单排序(如插入),大规模数据用快速/归并/堆排序,稳定性要求高则选归并排序。这条消息已经在编辑器中准备就绪。你想如何调整这篇文档?请随时告诉我。