首页 / 资讯中心 / 文章详情

十大经典排序算法详解与Python实现

十大经典排序算法详解与Python实现 ★ FEATURED ARTICLE
1. 排序算法基础概念与分类排序算法是计算机科学中最基础也最重要的算法类型之一。简单来说排序就是将一组数据按照特定顺序如数字大小、字母顺序等重新排列的过程。在实际开发中排序算法的应用无处不在从数据库查询优化到用户界面展示从数据分析到机器学习预处理。根据不同的实现方式和性能特点排序算法可以分为以下几大类比较类排序通过比较元素间的大小关系来决定排序顺序。这类排序算法的时间复杂度通常不会低于O(nlogn)因为每次比较只能获取有限的信息量。典型代表有冒泡排序、快速排序、归并排序等。非比较类排序不通过比较来决定元素顺序而是利用数据的其他特性如数值范围、位数等。这类排序在某些特定条件下可以达到线性时间复杂度O(n)。典型代表有计数排序、基数排序、桶排序等。稳定排序如果两个相等的元素在排序前后的相对位置保持不变则称该排序算法是稳定的。稳定性在某些应用场景中非常重要比如先按年龄排序再按姓名排序时需要保持同年龄者的姓名顺序。原地排序指不需要额外存储空间或只需要常数级别的额外空间的排序算法。这类算法对内存使用效率高适合处理大数据量。2. 经典排序算法详解2.1 冒泡排序(Bubble Sort)冒泡排序是最简单直观的排序算法之一。它的工作原理是重复地遍历要排序的列表比较相邻的元素并将顺序错误的元素交换位置。这个过程就像气泡从水底升到水面一样较小的元素会逐渐浮到列表的顶端。算法步骤比较相邻的元素。如果第一个比第二个大就交换它们。对每一对相邻元素做同样的工作从开始第一对到结尾的最后一对。这步做完后最后的元素会是最大的数。针对所有的元素重复以上的步骤除了最后一个。持续每次对越来越少的元素重复上面的步骤直到没有任何一对数字需要比较。时间复杂度分析最佳情况O(n)当输入数据已经是正序时最坏情况O(n²)当输入数据是反序时平均情况O(n²)空间复杂度O(1)原地排序稳定性稳定代码实现Pythondef bubble_sort(arr): n len(arr) for i in range(n-1): for j in range(n-i-1): if arr[j] arr[j1]: arr[j], arr[j1] arr[j1], arr[j] return arr优化技巧设置标志位当某一轮没有发生交换时说明已经有序可以提前结束排序。记录最后一次交换的位置下一轮只需要比较到这个位置即可。2.2 选择排序(Selection Sort)选择排序是一种简单直观的排序算法它的工作原理是每次从待排序的数据元素中选出最小或最大的一个元素存放在序列的起始位置然后再从剩余未排序元素中继续寻找最小大元素放到已排序序列的末尾。以此类推直到全部待排序的数据元素排完。算法步骤在未排序序列中找到最小大元素存放到排序序列的起始位置。从剩余未排序元素中继续寻找最小大元素然后放到已排序序列的末尾。重复第二步直到所有元素均排序完毕。时间复杂度分析最佳情况O(n²)最坏情况O(n²)平均情况O(n²)空间复杂度O(1)原地排序稳定性不稳定因为交换操作可能改变相等元素的相对位置代码实现Pythondef selection_sort(arr): n len(arr) for i in range(n-1): min_idx i for j in range(i1, n): if arr[j] arr[min_idx]: min_idx j arr[i], arr[min_idx] arr[min_idx], arr[i] return arr特点交换次数少最多进行n-1次交换。比较次数固定总是n(n-1)/2次。适合数据量小且对交换次数敏感的场景。2.3 插入排序(Insertion Sort)插入排序的工作方式类似于我们整理扑克牌的方式。它构建有序序列对于未排序数据在已排序序列中从后向前扫描找到相应位置并插入。算法步骤从第一个元素开始该元素可以认为已经被排序。取出下一个元素在已经排序的元素序列中从后向前扫描。如果该元素已排序大于新元素将该元素移到下一位置。重复步骤3直到找到已排序的元素小于或者等于新元素的位置。将新元素插入到该位置后。重复步骤2~5。时间复杂度分析最佳情况O(n)当输入数据已经是正序时最坏情况O(n²)当输入数据是反序时平均情况O(n²)空间复杂度O(1)原地排序稳定性稳定代码实现Pythondef 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[j1] arr[j] j - 1 arr[j1] key return arr优化技巧二分查找插入在已排序部分使用二分查找确定插入位置可以减少比较次数但移动元素的次数不变。希尔排序是插入排序的改进版通过分组插入排序来提高效率。2.4 快速排序(Quick Sort)快速排序是一种分治算法由Tony Hoare在1959年提出。它通过一趟排序将待排记录分隔成独立的两部分其中一部分记录的关键字均比另一部分的关键字小则可分别对这两部分记录继续进行排序以达到整个序列有序。算法步骤从数列中挑出一个元素称为基准(pivot)。重新排序数列所有元素比基准值小的摆放在基准前面所有元素比基准值大的摆在基准的后面相同的数可以到任一边。在这个分区退出之后该基准就处于数列的中间位置。这个称为分区(partition)操作。递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。时间复杂度分析最佳情况O(nlogn)最坏情况O(n²)当输入数据已经有序或逆序时平均情况O(nlogn)空间复杂度O(logn)递归调用栈的空间稳定性不稳定代码实现Pythondef 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)优化技巧三数取中法选择基准从待排序数组的首、中、尾三个位置取中间值作为基准可以有效避免最坏情况。小数组使用插入排序当子数组规模较小时如长度小于10切换为插入排序可以减少递归开销。尾递归优化将递归转换为迭代减少栈空间的使用。2.5 归并排序(Merge Sort)归并排序是建立在归并操作上的一种有效的排序算法由John von Neumann于1945年发明。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用且各层分治递归可以同时进行。算法步骤申请空间使其大小为两个已经排序序列之和该空间用来存放合并后的序列。设定两个指针最初位置分别为两个已经排序序列的起始位置。比较两个指针所指向的元素选择相对小的元素放入到合并空间并移动指针到下一位置。重复步骤3直到某一指针超出序列尾。将另一序列剩下的所有元素直接复制到合并序列尾。时间复杂度分析最佳情况O(nlogn)最坏情况O(nlogn)平均情况O(nlogn)空间复杂度O(n)需要额外的存储空间稳定性稳定代码实现Pythondef 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): result [] 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 result.extend(left[i:]) result.extend(right[j:]) return result特点稳定的排序算法适合处理大规模数据常用于外部排序数据量太大无法全部加载到内存时2.6 堆排序(Heap Sort)堆排序是利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构并同时满足堆的性质即子节点的键值或索引总是小于或者大于它的父节点。算法步骤将待排序序列构建成一个堆H[0..n-1]根据升序降序需求选择大顶堆或小顶堆。把堆首最大值和堆尾互换。把堆的尺寸缩小1并调用shift_down(0)目的是把新的数组顶端数据调整到相应位置。重复步骤2直到堆的尺寸为1。时间复杂度分析最佳情况O(nlogn)最坏情况O(nlogn)平均情况O(nlogn)空间复杂度O(1)原地排序稳定性不稳定代码实现Pythondef heapify(arr, n, i): largest i l 2 * i 1 r 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] 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(nlogn)不需要额外的存储空间适合处理大规模数据实际应用中不如快速排序快因为常数因子较大2.7 希尔排序(Shell Sort)希尔排序是插入排序的一种更高效的改进版本由Donald Shell于1959年提出。希尔排序通过将原始列表分割成若干子列表来进行插入排序子列表是通过间隔gap来创建的。算法步骤选择一个增量序列t1t2...tk其中titjtk1。按增量序列个数k对序列进行k趟排序。每趟排序根据对应的增量ti将待排序列分割成若干长度为m的子序列分别对各子表进行直接插入排序。仅增量因子为1时整个序列作为一个表来处理表长度即为整个序列的长度。时间复杂度分析最佳情况O(nlogn)最坏情况O(n²)取决于增量序列的选择平均情况O(nlogn)O(n²)空间复杂度O(1)原地排序稳定性不稳定代码实现Pythondef 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 return arr特点是插入排序的改进版时间复杂度取决于增量序列的选择在实际应用中表现良好特别是中等规模的数据2.8 计数排序(Counting Sort)计数排序是一种非比较的排序算法其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序计数排序要求输入的数据必须是有确定范围的整数。算法步骤找出待排序的数组中最大和最小的元素。统计数组中每个值为i的元素出现的次数存入数组C的第i项。对所有的计数累加从C中的第一个元素开始每一项和前一项相加。反向填充目标数组将每个元素i放在新数组的第C[i]项每放一个元素就将C[i]减去1。时间复杂度分析最佳情况O(nk)k是整数的范围最坏情况O(nk)平均情况O(nk)空间复杂度O(nk)需要额外的计数数组和输出数组稳定性稳定代码实现Pythondef counting_sort(arr): if not arr: return arr max_val max(arr) min_val min(arr) range_val max_val - min_val 1 count [0] * range_val output [0] * len(arr) for num in arr: count[num - min_val] 1 for i in range(1, len(count)): count[i] count[i-1] for num in reversed(arr): output[count[num - min_val] - 1] num count[num - min_val] - 1 return output特点非比较排序时间复杂度可以达到线性适合数据范围不大的整数排序需要额外的存储空间2.9 桶排序(Bucket Sort)桶排序是计数排序的升级版。它利用了函数的映射关系高效与否的关键就在于这个映射函数的确定。桶排序的工作的原理假设输入数据服从均匀分布将数据分到有限数量的桶里每个桶再分别排序有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序。算法步骤设置一个定量的数组当作空桶。遍历输入数据并且把数据一个一个放到对应的桶里去。对每个不是空的桶进行排序。从不是空的桶里把排好序的数据拼接起来。时间复杂度分析最佳情况O(nk)k是桶的数量最坏情况O(n²)所有数据都放在一个桶里平均情况O(nk)空间复杂度O(nk)稳定性稳定取决于桶内排序算法的稳定性代码实现Pythondef bucket_sort(arr, bucket_size5): 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: buckets[(num - min_val) // bucket_size].append(num) arr [] for bucket in buckets: insertion_sort(bucket) # 可以使用其他排序算法 arr.extend(bucket) return arr特点非比较排序适合数据分布均匀的情况可以处理浮点数排序需要额外的存储空间2.10 基数排序(Radix Sort)基数排序是一种非比较型整数排序算法其原理是将整数按位数切割成不同的数字然后按每个位数分别比较。由于整数也可以表达字符串比如名字或日期和特定格式的浮点数所以基数排序也不是只能使用于整数。算法步骤取得数组中的最大数并取得位数。arr为原始数组从最低位开始取每个位组成radix数组。对radix进行计数排序利用计数排序适用于小范围数的特点。时间复杂度分析最佳情况O(n*k)k是最大数字的位数最坏情况O(n*k)平均情况O(n*k)空间复杂度O(nk)稳定性稳定代码实现Pythondef counting_sort_for_radix(arr, exp): n len(arr) output [0] * n count [0] * 10 for i in range(n): index (arr[i] // exp) % 10 count[index] 1 for i in range(1, 10): count[i] count[i-1] i n - 1 while i 0: index (arr[i] // exp) % 10 output[count[index] - 1] arr[i] count[index] - 1 i - 1 for i in range(n): arr[i] output[i] def radix_sort(arr): max_val max(arr) exp 1 while max_val // exp 0: counting_sort_for_radix(arr, exp) exp * 10 return arr特点非比较排序适合位数不多但数值范围大的整数排序可以处理负数需要额外处理需要额外的存储空间3. 排序算法比较与选择指南3.1 时间复杂度与空间复杂度对比下表总结了各种排序算法的时间复杂度和空间复杂度排序算法最佳时间复杂度平均时间复杂度最坏时间复杂度空间复杂度稳定性冒泡排序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(nlogn)O(nlogn)O(n²)O(logn)不稳定归并排序O(nlogn)O(nlogn)O(nlogn)O(n)稳定堆排序O(nlogn)O(nlogn)O(nlogn)O(1)不稳定希尔排序O(nlogn)O(nlogn)O(n²)O(n²)O(1)不稳定计数排序O(nk)O(nk)O(nk)O(nk)稳定桶排序O(nk)O(nk)O(n²)O(nk)稳定基数排序O(nk)O(nk)O(nk)O(nk)稳定3.2 如何选择合适的排序算法在实际应用中选择排序算法需要考虑多个因素数据规模小规模数据n 100插入排序、选择排序、冒泡排序等简单排序可能更高效中等规模数据100 n 10,000快速排序、归并排序、堆排序等O(nlogn)算法大规模数据n 10,000快速排序、归并排序、堆排序等或考虑非比较排序数据特性几乎有序的数据插入排序表现最好大量重复元素三路快速排序更优数值范围有限考虑计数排序、基数排序等非比较排序数据分布均匀桶排序可能是不错的选择稳定性要求需要稳定排序归并排序、插入排序、冒泡排序、计数排序、桶排序、基数排序不需要稳定排序快速排序、堆排序、选择排序、希尔排序内存限制内存充足可以使用归并排序等需要额外空间的算法内存受限选择原地排序算法如堆排序、快速排序、希尔排序等实现复杂度快速排序、归并排序、堆排序等实现相对复杂插入排序、选择排序、冒泡排序等实现简单3.3 实际应用场景举例数据库排序通常使用改进的归并排序如Timsort需要稳定排序以保持多字段排序的正确性需要处理大规模数据编程语言内置排序Python的sorted()使用TimsortJava的Arrays.sort()对基本类型使用双轴快速排序对对象使用TimsortC的std::sort()使用内省排序快速排序堆排序大数据处理外部排序通常基于归并排序MapReduce等分布式计算框架中的排序也是基于归并思想图形界面排序表格数据的排序通常使用快速排序需要即时响应快速排序的平均性能最好游戏开发实时渲染中的深度排序可能使用基数排序粒子系统可能使用计数排序4. 排序算法优化与进阶技巧4.1 混合排序策略在实际应用中常常会结合多种排序算法的优点形成混合排序策略内省排序(Introsort)结合快速排序、堆排序和插入排序开始使用快速排序当递归深度超过一定限度时切换到堆排序对小规模子数组使用插入排序C的std::sort()采用这种策略Timsort结合归并排序和插入排序专门优化了现实世界中部分有序的数据Python和Java采用的默认排序算法快速排序插入排序快速排序递归到小规模子数组时切换到插入排序可以减少递归调用的开销4.2 并行排序算法随着多核处理器的普及并行排序算法变得越来越重要并行快速排序分区后将两个子数组分配给不同的线程处理需要注意负载均衡问题并行归并排序将数组分成多个部分分别排序后再合并合并阶段也可以并行化Bitonic排序专门为并行计算设计的排序网络适合在GPU上实现4.3 特殊数据结构的排序链表排序归并排序特别适合链表排序不需要额外空间快速排序在链表上实现较为复杂外部排序处理无法全部装入内存的大数据基于归并排序的思想需要多路归并字符串排序可以使用基数排序的变种需要考虑字符串长度不一的情况4.4 排序算法的可视化与调试理解排序算法的一个好方法是观察它们的执行过程可视化工具使用Python的matplotlib等库实现排序过程可视化观察元素如何移动和交换调试技巧在关键步骤打印数组状态检查递归深度和分区结果验证排序的稳定性性能分析使用时间戳测量不同规模数据的排序时间比较理论时间复杂度和实际运行时间5. 常见问题与解决方案5.1 排序算法常见错误边界条件处理不当空数组或单元素数组所有元素相同的情况已经有序或逆序的数组递归深度过大快速排序在最坏情况下递归深度为O(n)可能导致栈溢出解决方案限制递归深度或改用迭代实现稳定性问题需要稳定排序时选择了不稳定算法解决方案明确需求选择合适算法数据类型限制非比较排序通常只能用于整数解决方案根据数据类型选择算法5.2 性能优化技巧减少不必要的操作避免在内部循环中进行函数调用减少内存分配和释放利用局部性原理尽量顺序访问内存提高缓存命中率快速排序的分区策略影响缓存性能预处理数据对数据进行归一化处理将浮点数转换为整数如乘以10^6后取整选择合适的数据结构对链表使用归并排序对数组使用快速排序或堆排序5.3 实际应用中的注意事项API设计提供比较函数作为参数增强灵活性支持升序和降序排列考虑是否要原地排序内存管理注意临时数组的分配和释放避免内存泄漏多线程安全如果需要在多线程环境中使用确保线程安全可以使用线程局部存储或加锁错误处理处理无效输入如NULL指针检测数组越界6. 排序算法的扩展应用6.1 排序在算法问题中的应用Top K问题使用快速选择算法快速排序的变种可以在O(n)时间内解决也可以使用堆排序的思想中位数查找类似于Top K问题快速选择算法的典型应用去重问题先排序后去重是常见解决方案时间复杂度通常为O(nlogn)合并有序数组归并排序中的合并操作可以推广到多路归并6.2 排序在系统设计中的应用数据库索引B树、B树等索引结构依赖于排序范围查询需要数据有序搜索引擎搜索结果需要按相关性排序使用改进的快速排序或归并排序大数据处理MapReduce中的shuffle阶段本质上是分布式排序需要高效的外部排序算法操作系统进程调度需要考虑优先级排序内存页面置换算法如LRU需要维护访问顺序6.3 排序在机器学习中的应用特征排序按重要性对特征进行排序使用快速排序等高效算法K近邻算法需要按距离排序可以使用快速选择只找最近的K个决策树在节点分裂时需要排序特征值排序性能影响训练速度推荐系统对推荐结果进行排序可能需要稳定排序保持多样性
阅读完成 · 觉得有帮助?
咨询建站