插入排序

2024年8月29日 | 阅读 10 分钟

插入排序是一种基于比较的排序算法,它通过一次一个元素地构建最终的排序数组。它通过将输入数组划分为两个区域:已排序区域和未排序区域。最初,已排序区域只包含第一个元素,其余的数组被视为未排序。

算法逐一遍历未排序区域的每个元素,并将其与已排序区域中的元素进行比较。它通过将大于当前元素的元素向右移动来找到当前元素在已排序区域中的正确位置。这种移动为当前元素创建了一个插入空间。

为了执行插入,算法从已排序区域的最右边元素开始,并将其与当前元素进行比较。如果已排序区域中的元素更大,则将其向右移动一个位置。这个过程一直持续到找到当前元素的适当位置为止。

一旦确定了正确的位置,算法就会将当前元素插入到已排序区域。这个插入步骤会为未排序区域中的所有元素重复执行,逐渐扩展已排序区域,直到整个数组被排序。

插入排序是一种原地算法,意味着它不需要额外的内存来执行排序。它也被认为是一种稳定的排序算法,因为它保留了相等值元素的相对顺序。

在最坏的情况下,插入排序的时间复杂度为 O(n^2),其中 n 是输入数组中的元素数量。然而,在实践中,对于小型或部分排序的数组,它的性能可能会更好。在最好的情况下,当输入数组已经排序时,时间复杂度会降低到 O(n)。平均时间复杂度也为 O(n^2),与快速排序或归并排序等更高级的排序算法相比,效率较低。

尽管对于大型数组的性能相对较慢,但插入排序仍有一些优点。它易于理解和实现,使其成为小型数据集或偏好简单而非效率的情况下的合适选择。此外,它在部分排序或近乎排序的数组上表现良好,与其他算法相比,需要更少的比较和交换。

总之,插入排序是一种简单直观的排序算法,它通过将元素反复插入已排序区域中的正确位置来构建最终的排序数组。虽然对于大型数据集来说它可能不是最高效的选择,但它有其优点,在某些情况下可以成为一个有用的工具。

历史

插入排序算法有着悠久的历史,可以追溯到计算机科学的早期。它最早由计算机科学家和数学家约翰·冯·诺依曼(John von Neumann)于1945年提及,尽管该算法可能在此之前就已经为人所知。

插入排序的基本概念可以在纸牌游戏中找到,例如扑克牌或桥牌。玩家通常通过不断将一张新牌插入到已排序的牌组中的正确位置来整理自己的牌。这种手动排序技术启发了插入排序算法的发展。

随着计算机的出现以及对高效排序方法的需求,该算法获得了推广。在计算机的早期,内存有限,复杂的排序算法并不可行。插入排序因其简单性和高效排序小型数组的能力而成为一个受欢迎的选择。

在计算机编程的背景下,最早提及插入排序算法的文献之一可以在莫里斯·威尔克斯(Maurice Wilkes)于1951年出版的书《电子数字计算机程序准备》(The Preparation of Programs for an Electronic Digital Computer)中找到。威尔克斯将该算法描述为一种在计算机程序中对数字列表进行排序的技术。

多年来,插入排序得到了广泛的分析和研究。其时间复杂度和性能特征已被充分理解。研究人员将插入排序与其他排序算法进行了比较,并确定了其优缺点。

虽然插入排序对于大型数据集来说可能不是最有效的算法,但它在各种场景中都有其应用。当处理小型或部分排序的数组时,它特别有用,因为其简单性和良好的性能使其成为一个有吸引力的选择。

尽管开发了更高级的排序算法,插入排序仍然作为一种重要的排序技术被教授和研究。其直接的实现和直观的性质使其成为学习排序算法和理解计算机科学基本原理的宝贵工具。

插入排序算法

  1. 从定义一个名为 insertionSort 的函数开始,该函数接受数组 arr 和其大小 n 作为输入。
  2. 声明三个变量:i、key 和 j。
  3. 从索引 1 到 n-1 遍历数组。
  4. 将 arr[i] 的值赋给变量 key。
  5. 将 i-1 的值赋给 j。
  6. 当 j 大于或等于 0 且 arr[j] 的值大于 key 时,执行以下步骤:
  7. 将 arr[j] 的值赋给 arr[j+1]。
  8. 将 j 的值减 1。
  9. 将 key 的值赋给 arr[j+1]。
  10. 重复步骤 3-5,直到整个数组排序完成。
  11. 定义一个名为 printArray 的函数,该函数接受数组 arr 和其大小 n 作为输入。
  12. 从索引 0 到 n-1 遍历数组。
  13. 打印 arr[i] 的值。
  14. 打印一个新行以分隔输出。
  15. 排序算法已完成。
  16. 要使用 insertionSort 函数,您可以从 main 函数调用它,并将要排序的数组及其大小作为参数传递。同样,您可以使用 printArray 函数来显示排序后的数组。

下面是 C++ 插入排序算法的程序

输出

5 6 11 12 13
............
Process executed in 1.11 seconds
Press any key to continue.

说明

以下是给定代码的分步解释

  1. 代码以包含必要的头文件开始:<bits/stdc++.h>,它包含了大多数标准库头文件,以及 using namespace std;,它允许在不显式指定命名空间的情况下使用标准库函数和对象。
  2. 定义了 insertionSort 函数,它以数组 arr 和其大小 n 作为参数。此函数实现插入排序算法以升序对数组进行排序。
  3. 在 insertionSort 函数内部,一个循环从数组的第二个元素(i = 1)迭代到最后一个元素。每次迭代代表选择一个要插入到已排序子数组中的元素。
  4. 选定的元素存储在变量 key 中。变量 j 被设置为选定元素前面一个元素的索引。
  5. 一个 while 循环检查索引 j 处的元素是否大于 key,以及 j 是否在有效范围内(大于或等于 0)。此循环将大于 key 的元素向右移动一个位置。
  6. 在找到 key 元素的正确位置后,它被插入到数组的 j + 1 索引处。
  7. 定义了 printArray 函数,用于以空格分隔的格式打印大小为 n 的数组 arr 的元素。
  8. main 函数是程序的入口点。它初始化一个包含 {12, 11, 13, 5, 6} 值的数组 arr。
  9. 通过将数组的总大小除以单个元素的大小,将变量 N 赋值为数组 arr 的大小。
  10. 调用 insertionSort 函数,并将 arr 和 N 作为参数传递,以对数组进行排序。
  11. 调用 printArray 函数来显示排序后的数组。
  12. 最后,main 函数返回 0,表示程序成功执行。
  13. 输出显示为“5 6 11 12 13”,这代表了排序后的数组。
  14. 总而言之,代码演示了插入排序算法的实现,并以升序对给定数组进行了排序。

时间复杂度分析

此代码中实现的插入排序算法在最坏和平均情况下的时间复杂度为 O(n2),在最好情况下的时间复杂度为 O(n)。

主循环运行 O(n) 次迭代(其中 O(n) 是数组的大小),对于每次迭代,内部 while 循环最多可能运行 i 次,其中 i 是外层循环中的当前索引。在最坏的情况下,当数组是反向排序的时,内部循环将运行其最大次数,导致 O(n2) 的时间复杂度。在最好的情况下,当数组已经排序时,内部循环不会执行,时间复杂度为 O(n)。

空间复杂度分析

代码的空间复杂度为 O(1)。原因是该算法以原地方式执行排序,不使用任何额外的、其空间需求取决于输入大小的数据结构。唯一使用的额外空间是像 i、key 和 j 这样的变量,它们的大小是常数,与输入数组大小无关。

总之,在最坏和平均情况下,时间复杂度为 O(n2),在最好情况下为 O(n),而空间复杂度为 O(1),因为算法使用了恒定的额外空间。

插入排序的优点

  • 简单性:插入排序易于理解和实现。它使用简单的比较和交换机制来对元素进行排序。
  • 对小型输入的效率:当输入大小很小或数组已经部分排序时,插入排序表现良好。与更复杂的排序算法相比,它的开销相对较低。
  • 在线排序:插入排序是一种在线排序算法,这意味着它可以高效地处理逐个进入的数据。此特性使其在数据不断被添加或动态排序的场景中很有用。
  • 空间效率:插入排序在原地操作,这意味着它不需要额外的内存来执行排序。它在现有数组本身内对元素进行排序。

插入排序的缺点

  • 二次时间复杂度:插入排序的平均和最坏情况时间复杂度为 O(n^2),这使得它对于大型输入大小效率低下。随着元素数量的增加,所需的比较和移动次数呈指数级增长。
  • 缺乏适应性:插入排序不能很好地适应已排序或近乎排序的数组。即使数组部分排序,它仍然会执行一套完整的比较和移动。
  • 不适合大型数据集:由于其二次时间复杂度,对于排序大型数据集来说,插入排序变得不切实际。在这种情况下,归并排序或快速排序等其他排序算法更有效。
  • 性能依赖于输入顺序:插入排序的效率在很大程度上取决于元素的初始顺序。如果数组是反向排序的,插入排序将需要最大数量的比较和移动,从而进一步增加其时间复杂度。

总之,虽然插入排序具有简单性、空间效率,并且在小型输入或部分排序的数组上表现良好,但其二次时间复杂度和缺乏适应性使其不适合大型数据集或已排序数组。在选择排序算法时,请考虑输入数据的特性和所需的性能要求。

插入排序的应用

插入排序是一种简单而高效的排序算法,可应用于各种场景。以下是插入排序的一些常见应用:

  • 小型数据集:插入排序在小型数据集或已部分排序或近乎排序的数组上表现良好。它的开销相对较低,并且在这种情况下效率很高。
  • 在线排序:插入排序通常用于实时或可用时对数据进行排序。它允许增量排序,其中新元素可以有效地插入到已排序的列表中。
  • 部分排序:在某些情况下,只需要对数据的一部分进行排序。插入排序在这种情况下很有效,因为它可以在不对剩余元素进行任何操作的情况下对一小部分数据进行排序。
  • 辅助排序算法:插入排序经常用作其他排序算法内的辅助算法。例如,在快速排序或 Timsort 等算法中,插入排序用于对小型子数组进行排序,或在数组大小变小时作为备用。
  • 教育目的:由于其简单性和易于理解性,插入排序通常在计算机科学和编程课程中进行教学。它作为排序算法的一个基本示例,帮助学生掌握排序的基本概念。
  • 针对链表优化:插入排序非常适合对链表进行排序,因为它只需要恒定的额外空间。它有效地重新排列节点之间的链接,使其成为对链接数据结构进行排序的实用选择。

总而言之,插入排序对于大规模或高度未排序的数据集来说可能不是最有效的排序算法。然而,它的简单性、对特定场景的适用性以及教育价值使其成为各种应用中的宝贵算法。