了解插入排序

到目前为止,我们已经了解了两种基于比较的排序算法。现在,我们将探讨另一种排序算法,与前两种算法相比,它具有一定的效率。我们现在讨论的是插入排序。与我们刚才看到的其他两种排序算法相比,它的实现最为简单。如果项目数较少,插入排序比冒泡排序和选择排序更有效。如果数据集较大,那么它就会像冒泡排序一样效率低下。由于插入排序的交换几乎是线性的,因此建议使用插入排序而不是冒泡排序和选择排序。

顾名思义,插入排序的工作原理是将数字插入左侧的正确位置。它从数组的第二个项目开始,检查左边的项目是否小于当前值。如果是,它就会移动项目,并将较小的项目存储到正确的位置。然后,它会移动到下一个项目,同样的原则一直持续到整个数组排序完毕。插入排序的伪代码是这样的:

procedure insertionSort( A : list of sortable items )
    n = length(A)
    for i = 1 to n inclusive do
        key = A[i]
        j = i - 1
        while j >= 0 and A[j] > key do
            A[j+1] = A[j]
            j--
        end while

        A[j+1] = key

    end for
end procedure

如果我们考虑之前用于冒泡排序和选择排序的数字列表,那么就会出现以下情况,我们必须进行插入排序。我们数组中的元素是 20, 45, 93, 67, 10, 97, 52, 88, 33, 92.

让我们从第二个项目开始,即 45。现在,我们从 45 左边的第一项开始,到数组的开头,看看左边是否有大于 45 的值。由于只有 20,所以不需要插入,因为目前的项目已经排序到第二个元素(20,45)。现在,我们将指针移到 93,然后重新开始,从 45 开始比较数组左边的值,并搜索是否有更大的值。由于 45 不比 93 大,它就停止在这里,因为我们之前的结论是前两个项目已经排序。现在,我们对前三个项目(20、45、93)进行了排序。接下来是 67,我们再次从左边的数字开始比较。左边的第一个数字是 93,它的数字较大,因此必须移动一位。我们将 93 移到 67 的位置。然后,我们移动到它左边的下一个数字,即 45。45 比 67 小,不需要进一步比较。现在,我们将在 93 的位置上插入 67,93 必须移动到 67 的位置上。这样一直进行下去,直到整个数组排序完毕。

下图说明了在每个步骤中使用插入排序的完整排序过程:

image 2023 11 08 11 57 32 987

实现插入排序

我们将以与其他两种排序类似的方式实现插入排序,但有一个微妙的区别。这次,我们将以引用的形式传递数组。这样,我们就不需要函数的任何返回值了。如果需要,我们也可以通过值传递参数,并在函数结束时返回数组。下面是相关代码:

Unresolved include directive in modules/ROOT/pages/ch07/ch7-04.adoc - include::example$Chapter07/6.php[]

参数数组通过引用 (&$arr) 传递给函数。因此,将直接修改原始数组,而不是其副本。现在,我们要运行代码并检查输出结果。为此,我们必须运行以下代码:

Unresolved include directive in modules/ROOT/pages/ch07/ch7-04.adoc - include::example$Chapter07/6.php[]

如果我们以引用方式传递数组,则不必返回数组。传递的数组将在函数内部被修改。我们可以选择如何实现排序。

插入排序的复杂度

插入排序的复杂度与冒泡排序相似。与冒泡排序的基本区别在于,交换次数比冒泡排序少得多。下面是插入排序的复杂度:

最好时间复杂度

\$Ω(n)\$

最坏时间复杂度

\$O(n^2)\$

平均时间复杂度

\$Θ(n^2)\$

空间复杂度(最差情况)

\$O(1)\$