对近乎排序的数组进行排序2025年3月17日 | 阅读 10 分钟 引言排序算法是数据处理和计算机科学的重要组成部分。尽管有许多不同的排序算法,但每种算法的有效性取决于需要排序的数据的属性。排序一个近似排序的数组,其中每个元素距离其排序位置最多有 k 个位置,这是一个有趣的情况。近似排序数组是一种特殊情况,其中元素在排序的顺序上有所不同,但并不完全相同。一个关键的特征是,每个元素距离其最终排序位置最多有 k 个位置。与通用排序技术相比,这一特性使得排序算法更有效。 插入排序插入排序技术是最直接和有效的排序算法之一,尤其是在用于排序近似排序数组时,这是一种特殊情况。该算法通过一次一个地将每个元素逐渐添加到最终排序的数组中来工作。将数组分成已排序子数组和未排序子数组是其有效性的关键。当它遍历未排序部分时,算法会比较每个元素并将其移动到已排序子数组的适当位置。在处理近似排序的数组,其中每个元素距离其排序位置最多有 k 个位置时,此过程尤其有用。 通过利用当前顺序并减少所需的比较和交换次数,插入排序算法在此情况下表现良好。虽然 O(n^2) 的最坏情况时间复杂度可能看起来是一个缺点,但由于它在处理小数据集和部分有序数据集方面表现出色,因此它实际上是一个有用且实用的选择。通过限制元素的移动(由参数 k 表示),插入排序有效地将近似排序的数组转换为完全有序的对应数组,这表明了该算法在特定排序情况下的适应性和强大能力。 代码 输出  代码解释 函数声明 - 程序开头包含了所需的头文件,并声明了 insertionSort 函数。该函数需要三个参数:数组的大小 n、最大位移 k 和整数数组 arr。
插入排序的逻辑 - insertionSort 函数是程序的核心,负责实际排序。它遵循插入排序算法,该算法从第二个元素(i = 1)开始遍历数组。
- 它将每个元素与已排序子数组中的元素(即当前位置之前的元素)进行比较。
- 为了给当前元素腾出空间,已排序子数组中大于当前元素的元素会向右移动。
- 重复此过程,直到当前元素在已排序子数组中找到正确的位置并插入其中。
主函数 - 在 main 函数中,声明并初始化了一个示例数组 arr,其值为 {12, 11, 13, 5, 6}。
- 为了在数组大小发生变化时提供灵活性,使用 sizeof 运算符计算数组大小 n。
- 最大位移 k 设置为三。
- 之后,将数组、其大小和最大位移作为参数传递给 insertionSort 函数。
打印已排序的数组 - 排序完成后,在 main 函数中通过一个简单的循环将已排序的数组打印到控制台。
输出 - 程序最终运行得到的已排序数组显示了插入排序算法对提供的近似排序数组进行排序的效果。
时间复杂度 - 最坏情况:最坏情况发生在新入元素需要与已有序序列中所有元素比较并移动到正确位置时,时间复杂度为 O(n^2)。
- 最佳情况:O(n) - 当数组已基本排序,只需很少或无需元素移动时发生。
空间复杂度 - O(1):插入排序是一种原地排序算法,其临时变量占用的额外内存是恒定的。与输入大小成比例的额外空间不是必需的。
堆排序堆排序是一种流行的排序算法,在元素近似排序的数组中表现良好。该算法利用了二叉堆这一特殊的基于树的数据结构。当应用于近似排序的数组(其中每个元素距离其排序位置最多有 k 个位置)时,堆排序在快速重新排列数据方面表现出色。该算法主要包括堆化和排序两个阶段。在堆化过程中,数组被转换为最大堆,确保每个父节点都大于或等于其所有子节点。 建立最大堆后,排序阶段包括减小堆的大小,恢复最大堆属性,并重复地交换根(最大元素)与最后一个元素。重复此过程,直到整个数组排序完毕。对于大型数据集,堆排序的 O(n * log(n)) 时间复杂度非常有效。它能够利用现有顺序的能力表明其对近似排序数组的适应性,从而在各种情况下都能提供可靠的排序算法。 代码 输出  代码解释 头文件 - 代码包含所需的头文件:<stdlib.h> 用于动态内存分配和其他实用函数,<stdio.h> 用于输入输出操作。
heapify 函数 - 该函数负责维护最大堆的属性。
- 该函数需要三个参数:数组 arr、其大小 n 以及表示子树根的索引 i。
- 该函数通过识别当前节点的左子节点 (l) 和右子节点 (r) 来找到最大的子节点和根节点。
- 如果最大的不是根节点,它会交换元素并对受影响的子树递归调用 heapify。
heapSort 函数 - 堆排序的主要功能。需要三个参数:数组 arr、其大小 n 和最大位移 k。
- 第一步是从输入数组构建一个最大堆。每次从最后一个非叶节点到根节点进行迭代时,循环都会调用 heapify。
- 构建最大堆后,开始第二阶段。通过将最后一个元素与最大元素(根节点)交换来减小堆的大小。
- 然后,为了保持最大堆属性,对根节点调用 heapify。重复此过程,直到整个数组排序完毕。
main 函数 - 在 main 函数中,声明并初始化了一个示例数组 arr,其值为 {12, 32, 45, 57, 62}。
- 为了在数组大小发生变化时提供灵活性,使用 sizeof 运算符计算数组大小 n。
- 最大位移 k 设置为三。
- 接下来,将数组、其大小和最大位移作为参数传递给 heapSort 函数。
打印已排序的数组 - 排序完成后,在 main 函数中通过一个简单的循环将已排序的数组打印到控制台。
输出 - 程序最终输出的已排序数组显示了堆排序算法对输入数组进行排序的效果。
时间复杂度 - 最坏情况: O(n log n) - 堆排序对于处理大型数据集非常有效,因为它在所有情况下都始终保持 O(n log n) 的时间复杂度。
- 最佳情况: O(n log n) - 堆排序在性能一致性至关重要的场合表现良好,因为其最佳情况时间复杂度等于最坏情况时间复杂度。
空间复杂度 - O(1) - 堆排序是一种原地排序算法,它持续使用额外的变量内存。与输入大小成比例的额外空间不是必需的。
合并排序归并排序是一种非常有效的方法,尤其是在排序近似排序的数组时。该算法使用分治策略,将数组划分为更小的部分,分别对每个部分进行排序,然后将它们合并以创建最终的有序数组。归并排序的基本思想是它能够处理近似排序的数组,其中每个元素距离其排序位置最多有 k 个位置。通过将数组分成更小的部分,归并排序减少了位移的影响,从而实现了更高效的排序过程。算法的一个重要组成部分是合并函数,它巧妙地将两个已排序的数组合并成一个已排序的数组。 这种方法对于近似排序的数组效果很好,因为它能保持当前顺序并最大程度地减少比较和移动的总数。归并排序因其 O(n log n) 的时间复杂度而对于排序大型数据集非常有效。尽管如此,它在排序过程中也扮演着重要角色,因为其对近似排序数组的特殊情况的响应具有灵活性。归并排序是一种可靠的选项,用于对近似排序的数组进行排序,因为它系统的方法和对部分有序数据集的有效性,这展示了该算法的适应性和效率。 代码 输出  代码解释 头文件和函数声明 - 在程序包含所需的头文件 <stdio.h> 和 <stdlib.h> 后,声明了 merge 和 mergeSort 两个函数。
merge 函数 - merge 函数是归并排序算法的重要组成部分。数组 arr、索引 l(左)、m(中间)和 r(右)是其四个必需参数。
- 它确定了需要合并的两个子数组 L 和 R 的大小。
- 然后创建临时数组 L 和 R,并将原始数组中的数据复制到其中。
- 随后,该函数将两个临时数组合并到原始数组中,并相应地进行排序。
mergeSort 函数 - 执行归并排序算法的主要递归函数是 mergeSort 函数。
- 数组 arr 和索引 l(左)、m(中间)和 r(右)是其四个必需参数。
- 当 l 小于 r 时,递归的基例表示子数组至少包含两个元素。
- 为了将数组分成两半,函数会找到中间索引 m。
- 对于数组的左侧和右侧,都会调用 mergeSort 的递归调用。
- 最后,通过调用 merge 函数合并已排序的两个部分。
主函数 - 在 main 函数中,声明并配置了一个示例数组 arr,其值为 {12, 27, 42, 52, 64}。
- 使用 sizeof 运算符确定数组大小 n。
- 最大位移 k 设置为三。
- 接下来,将数组、其大小和最大位移作为参数传递给 mergeSort 函数。
打印已排序的数组 - 排序完成后,在 main 函数中通过一个简单的循环将已排序的数组打印到控制台。
输出 - 程序最终输出的已排序数组显示了归并排序算法对输入数组进行排序的效果。
时间复杂度 - 最坏情况: O(n log n) - 归并排序对于大型数据集非常有效,因为它在所有情况下都始终显示 O(n log n) 的时间复杂度。
- 最佳情况: O(n log n) - 归并排序在性能一致性至关重要的场合表现良好,因为其最佳情况时间复杂度与最坏情况时间复杂度相同。
空间复杂度 O(n) - 组合:该排序的空间复杂度与输入数组的大小直接相关。为了在合并过程中创建临时数组,需要额外的内存。与插入排序和堆排序等原地排序算法相比,其空间复杂度不太理想。
|