七种排序算法比较与选择[Python ]
七种排序算法比较与选择[Python ]
- 1. 冒泡排序(Bubble Sort)
- 2. 选择排序(Selection Sort)
- 3. 插入排序(Insertion Sort)
- 4. 希尔排序(Shell Sort)
- 5. 快速排序(Quick Sort)
- 6. 归并排序(Merge Sort)
- 7. 堆排序(Heap Sort)
- 总结
1. 冒泡排序(Bubble Sort)
原理:重复遍历列表,每次比较相邻元素,若顺序错误则交换,直到没有元素需要交换(大元素像气泡一样"浮"到末尾)。
时间复杂度:O(n²)(最坏/平均),O(n)(最好,已排序时)
空间复杂度:O(1)(原地排序)
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)(原地排序)
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)(原地排序)
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] = keyreturn 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)(原地排序)
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 //= 2return 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)(递归栈开销)
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或1if 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)(需要额外空间存储临时列表)
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 += 1else: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)(原地排序)
def heap_sort(arr):n = len(arr)# 构建大顶堆的辅助函数(调整以i为根的子树为大顶堆)def heapify(i, heap_size):# 初始化最大值索引为当前根节点ilargest = 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) | 不稳定 | 利用堆结构,无需额外空间 |
根据场景选择合适的算法:小规模数据用简单排序(如插入),大规模数据用快速/归并/堆排序,稳定性要求高则选归并排序。这条消息已经在编辑器中准备就绪。你想如何调整这篇文档?请随时告诉我。