C++ 排序算法

2025年3月17日 | 阅读19分钟

排序算法是计算机科学和数据处理中的基础工具。它们能够按特定顺序排列数据元素,从而更方便地搜索、检索和分析信息。排序是各种应用中的基本操作,从数据库管理到网络搜索,再到科学模拟和视频游戏机制。

Sorting Algorithms in C++

排序算法的必要性

在数字世界中,数据经常以未排序或倾斜排列的形式出现。例如,当您在搜索引擎中进行搜索时,搜索结果通常会按相关性排序显示。借助排序算法,您可以根据价格、评分或其他特征快速过滤电子商务网站上的产品列表。同样,在处理大型数据集或数据库时,有效的 数据检索也需要排序。

排序算法概述

排序算法根据预先设定的标准将数据项排列成特定的顺序。它们可以分为若干类,每类都有独特的特征和应用。排序算法的主要类别包括:

  1. 基于比较的排序算法:这些算法会比较数据元素,并在必要时交换顺序错误的元素。
    • 比较排序:基于比较的排序算法的示例包括冒泡排序、插入排序、选择排序、归并排序和快速排序。这些算法依赖于元素之间的比较来确定它们的相对顺序。
    • 非比较排序:非比较排序算法,如计数排序、基数排序和桶排序,不依赖于元素比较。它们在特定场景下通常比比较排序更有效,但也有其局限性。
  2. 自适应排序算法:根据数据的原始顺序,自适应排序算法会调整其性能。它们对于部分有序的数据可能更快,对于完全随机的输入则可能更慢。
    • 自适应比较排序:一些基于比较的排序算法,如插入排序,是自适应的,并且在给定部分有序数据时表现良好。
  3. 稳定排序算法:稳定排序算法用于保持相等元素的相对顺序。换句话说,即使排序后,如果两个元素的键相同,它们的位置也不会改变。
    • 稳定比较排序:例如冒泡排序和归并排序。
  4. 原地排序算法:原地排序算法无需额外的内存即可对数据进行排序。它们在现有数据结构内重新排列元素。
    • 原地比较排序:选择排序和快速排序是原地排序算法。
  5. 外部排序算法:这些算法用于处理无法完全放入内存的数据。它们包括将数据分解成小块,在内存中排序,然后合并。
    • 外部归并排序:数据库系统中常用的外部排序算法。
  6. 并行排序算法:并行排序算法利用多个处理器或核心并发地对数据进行排序,从而减少对大型数据集的排序时间。
    • 并行归并排序:归并排序的并行版本。

排序算法的工作原理

排序算法在计算机科学和数据处理中用于按特定顺序排列数据项。尽管有许多不同的排序算法,但它们都旨在将数据片段按给定顺序排列,无论是升序还是降序,都基于指定的标准。

  1. 比较或评估:排序算法首先比较数据集中的元素对。比较通常基于每个元素的特定键或属性。
  2. 交换或重排:当排序算法识别出两个元素顺序错误时,它会启动一个交换操作。此操作会重新排列这两个元素的位置,从而有效地将它们置于正确的顺序。交换操作对于重新组织数据元素并确保它们处于期望的顺序至关重要。
  3. 迭代或递归:比较和交换步骤会迭代地或递归地重复,直到整个数据集排序完成。完成排序过程所需的迭代或通道数取决于算法的设计和数据的初始状态。某些算法比其他算法需要更少的通道才能获得相同的结果。
  4. 最终结果:一旦所有迭代或递归调用完成,结果就是一个已排序的数据集。此数据集现在处于期望的顺序,可以是升序或降序,具体取决于所选标准(例如,数值、字母顺序)。

排序算法

  1. 冒泡排序
    • 一种简单的基于比较的排序算法。
    • 它会重复遍历列表,比较相邻的元素,如果顺序错误则进行交换。
    • 对于大型数据集效率不高,但易于理解和实现。
  2. 插入排序
    • 一次构建最终的排序数组。
    • 它从未排序部分取一个元素,并将其插入到已排序部分的正确位置。
    • 对于小型数据集或部分排序的数据很有效。
  3. 选择排序
    • 将输入分成一个已排序区域和一个未排序区域。
    • 它会重复从未排序区域选择最小的元素并将其移到已排序区域。
    • 简单但对于大型数据集效率不高。
  4. 合并排序
    • 一种分而治之的算法,将未排序的列表分成 n 个子列表,每个子列表包含一个元素。
    • 它会重复合并子列表以产生新的已排序子列表,直到只剩下一个子列表。
    • 以其稳定性和一致的性能而闻名。
  5. 快速排序
    • 另一种分而治之的算法。
    • 它从数组中选择一个“枢轴”元素,并将其他元素根据它们小于或大于枢轴进行分区。
    • 对于大型数据集效率很高,并且经常在实践中使用。
  6. 堆排序
    • 从数据构建二叉堆,并重复删除最大(对于最大堆)或最小(对于最小堆)元素。
    • 高效且时间复杂度为 O(n log n),但不是稳定排序算法。
  7. 基数排序
    • 通过处理单个数字来对数字进行排序。
    • 可用于整数或字符串,其中一次处理一个数字或字符。
    • 对于某些类型的数据可能非常高效。
  8. 计数排序
    • 适用于排序指定范围内的整数。
    • 它计算每个元素的频率,并利用此信息将元素置于排序顺序中。
    • 快速且稳定,但不适用于值范围很广的数据集。
  9. 桶排序
    • 将数据分成有限数量的桶,然后对每个桶进行单独排序。
    • 适用于分布式数据,并且当数据分布均匀时可能非常高效。
  10. 希尔排序
    • 插入排序的扩展,其中比较和交换指定间隔的元素。
    • 每次传递时间隔都会减小,直到整个数组都排序完成。
    • 在效率方面提供了插入排序和快速排序之间的平衡。

C++ 冒泡排序算法

  1. 从列表顶部开始向下遍历。
  2. 评估前两个元素。如果第一个元素大于第二个元素,则交换这两个元素。
  3. 重复步骤 2 和 3 处理接下来的元素组,以遍历元素列表。
  4. 重复步骤 2 和 3 直到列表完成。
  5. 遍历一次后,最大的未排序项将“冒泡”到列表的末尾。
  6. 重复步骤 1 到 5 将对列表的其余未排序元素进行排序(除了最后一个元素,它已经处于正确的位置)。
  7. 重复这些步骤直到所有元素都通过归并排序条件且不再需要交换,此时列表将被视为已排序。

源代码

C++ 中的归并排序

  1. 通过找到中间点将未排序的数组分成两半。
  2. 递归地排序左半部分和右半部分。
  3. 将已排序的半部分合并回一起,以生成单个已排序的数组。

归并排序的关键步骤是合并过程,其中将两个已排序的数组合并成一个已排序的数组。此步骤涉及比较两个数组中的元素,并将它们按排序顺序放入临时数组中。

源代码

插入排序

插入排序是一种简单的排序技术,可一次构建最终的排序数组。与快速排序、堆排序或归并排序等更复杂的算法相比,它在大型列表上的性能要差得多。但是,它有一些优点,例如对于小型数据集或已经经过部分排序的列表来说,它快速且易于使用。

源代码

选择排序

选择排序是一种简单的排序技术,它会不断地将数组未排序部分中最小(或最大,取决于排序顺序)的元素放在已排序部分的开头。其时间复杂度为 O(n2),因此不适用于大型数据集,但对于小型列表或作为更复杂排序算法的组件可能很有用。

  1. 从数组的未排序部分找到最小(或最大)元素。
  2. 将最小(或最大)元素与未排序部分的第一个元素交换。
  3. 将已排序和未排序部分之间的边界向右移动一个元素。
  4. 重复步骤 1-3,直到整个数组都排序完成。

快速排序

快速排序是一种基于比较的分而治之的排序算法。它的工作原理是选择数组中的一个元素作为“枢轴”元素,并将剩余的项根据它们小于或大于枢轴进行分区。然后递归地对子数组进行排序。完成此操作后,数组将完全排序。快速排序是最快的排序算法之一,平均和最佳情况下的时间复杂度为 O(n log n)。但在最坏情况下,它可能变为 O(n2)。

  1. 从数组中选择一个枢轴元素。
  2. 分区:重新排列数组,使所有小于枢轴的元素都放在它前面,所有大于枢轴的元素都放在它后面。枢轴现在处于其已排序的位置。
  3. 递归地对左侧(包含小于枢轴的元素)和右侧(包含大于枢轴的元素)的子数组使用快速排序。
  4. 持续执行此操作,直到整个数组都排序完成。

源代码

堆排序

基于比较的堆排序方法利用了二叉堆数据结构的优势。它在将输入数组分成已排序和未排序区域后,会重复地从未排序区域中删除最大(对于最大堆)或最小(对于最小堆)的元素,并将其添加到已排序区域。完成此操作后,数组将完全排序。堆排序是一种原地排序算法,时间复杂度为 O(n log n)。

  1. 首先,使用输入数组生成二叉堆。在通常表示为数组的二叉堆中,索引 i 处的每个条目都有两个子节点,分别位于位置 2*i + 1 和 2*i + 2。二叉堆可以是最大堆或最小堆,具体取决于您是想升序还是降序排序。
  2. 最大堆或最小堆的根处分别是未排序区域中值最大或最小的元素。此元素应被未排序区域中最后一个元素替换。
  3. 减小堆的大小(排除最后一个元素),并恢复堆属性(堆化根节点)。
  4. 重复步骤 2 和 3,直到堆为空。已排序的元素将累积在数组的末尾,对于最大堆是降序,对于最小堆是升序。

源代码

基数排序

基数排序是一种非比较排序方法,它根据对象包含的特定数字或字符将其分成桶。从最低有效位(最右边)到最高有效位(最左边)处理数字或字符。在每次传递中,元素会根据当前数字的值重新排列到桶中。处理完所有数字后,将元素按放入桶的顺序连接起来,形成一个已排序的数组。

  1. 为了确定数字的位数,找到数组中最大的数字。
  2. 对于每个数字位置,从最低有效数字到最高有效数字
  3. 初始化空桶(整数基数为 10 时为 0-9)。
  4. 根据当前数字,将每个元素放入相应的桶中。
  5. 合并桶以创建新数组,该数组已排序。
  6. 对于每个数字位置,重复步骤 2。
  7. 现在数组已排序。

C++ 代码

计数排序

计数排序是一种高效的非比较排序方法,用于排序值范围较窄的对象或数字。为了将元素置于正确的排序顺序中,它会计算输入数据中每个唯一元素的频率。

  1. 找到输入数组的最小值和最大值。
  2. 创建一个计数数组来存储范围内每个唯一元素的计数。
  3. 遍历输入数组并为每个元素递增计数。
  4. 计算计数数组中计数的累积和。此步骤有助于确定每个元素在已排序输出中的正确位置。
  5. 初始化一个输出数组来存储已排序的元素。
  6. 逆序遍历输入数组(以保持稳定性),并根据累积计数将每个元素放置到输出数组中的正确位置。
  7. 输出数组现在包含已排序的元素。

C++ 代码

桶排序

桶排序是一种排序算法,它通过将输入分成固定数量的等距“桶”,将元素分布到这些桶中,然后对每个桶进行单独排序,通常使用插入排序或快速排序等其他排序算法。在对每个桶进行排序后,您将所有已排序的桶连接起来以获得最终的已排序数组。

  1. 决定程序的桶数以及将接受的输入值的范围。
  2. 创建一个数组,其中包含一组空桶,以代表范围内的每个值。
  3. 遍历输入数组,使用映射函数将每个条目分配到相应的桶。使用映射函数,值应在桶之间均匀分布。
  4. 单独对每个桶进行排序,如果桶的大小足够大,则可以使用其他排序算法或递归地应用桶排序算法。
  5. 连接所有已排序的桶以获得最终的已排序数组。

源代码

希尔排序

希尔排序是一种高效、原地且不稳定的排序算法,它是插入排序算法的扩展。它通过重复排序相距较远的元素来工作,逐渐减小要比较的元素之间的间隙,直到间隙变为 1。此时,算法等同于简单的插入排序,但由于之前的排序传递,数组已部分排序,这可以显著提高插入排序的性能。

  1. 选择一个间隙值序列(通常称为“增量序列”),该序列以相对较大的间隙开始并逐渐减小它。常见的序列包括 Knuth 序列 (3k+1) 或每次传递将间隙除以 2。
  2. 从最大的间隙开始,对原始数组的子数组重复执行插入排序,其中每个子数组中的元素以选定的间隙值分隔。
  3. 逐渐减小间隙并重复步骤 2,直到间隙等于 1,这等同于对整个数组进行最终的插入排序传递。

源代码

排序算法的应用

  1. 数据检索:排序算法用于从数据库和文件系统中高效地检索数据。例如,数据库通常使用排序来快速查找满足特定标准的记录。
  2. 搜索算法:许多搜索算法,如二分搜索,需要先对数据进行排序。通过排序可以快速在列表中找到特定项。
  3. 安排结果:在搜索引擎(如 Google)、电子商务网站和社交媒体平台等应用程序中,使用排序算法来安排最终用户请求的结果、项目或帖子,顺序依据相关性、用户偏好或其他因素,例如产品是否为赞助内容。
  4. 数据分析:可以对数据和统计信息进行排序,以查找模式、异常值和趋势。例如,按时间分组的数据可以显示出时间趋势。
  5. 文件和目录列表:文件系统使用排序算法按顺序显示文件和目录,使人们更容易查找和管理文件。
  6. 任务调度:调度算法通常需要根据优先级、截止日期或其他标准对任务或进程进行排序。
  7. 优化问题:一些优化问题涉及将排序作为中间步骤。例如,旅行商问题可以通过按邻近度对城市进行排序来获得改进。
  8. 图算法:在图算法(如用于最小生成树的 Kruskal 算法)中,根据边的权重进行排序是关键步骤。
  9. 图像处理:图像处理应用程序可能使用排序来根据强度、颜色或其他属性对像素进行排序。
  10. 自然语言处理 (NLP):在 NLP 中,排序可用于按相关性或其他标准对文档或句子进行排序。
  11. 遗传学:遗传算法可能涉及根据适应度或其他遗传特征对种群中的个体进行排序。
  12. 计算机图形学:渲染和动画应用程序有时会使用排序来确定对象的绘制顺序,同时考虑它们的深度或透明度。
  13. 负载均衡:在分布式系统和云计算中,负载均衡算法可能会对任务或数据进行排序,以在资源之间平均分配负载。
  14. 路由和导航:在 GPS 和地图应用程序中,排序算法可能用于根据距离或时间对航点或路线进行排序。
  15. 协同过滤:推荐系统经常使用排序来根据用户的偏好和行为对其进行排名和推荐。
  16. 金融和股票市场分析:为了发现趋势并做出投资决策,金融数据(如股票价格)使用算法进行排序。
  17. 游戏和模拟:可以使用排序来根据多种标准(如接近度、速度或重要性)排列对象、角色或事件。

下一个主题C++ 中的 Calloc