[动画+注释详解]数据结构 - 直接插入排序

发布于:2024-05-06 ⋅ 阅读:(24) ⋅ 点赞:(0)

一. 直接插入排序算法的实现

1.1 基本思想

直接插入排序(Straight Insertion Sort)是一种简单直观的排序算法,它的基本思想是将一个待排序的记录插入到已经排序好的有序表中,从而得到一个新的、记录数增加1的有序表。

实际中,我们玩扑克牌时,就用到了插入排序的思想

1.2 代码的分析与详解

1. 新手村 - 有序区间的单趟排序

欢迎来到新手村,只有我们把单趟排序的逻辑先搞清楚,我们才能够真正熟练掌握直接插入排序算法

对于单趟的逻辑,我们把已排好的最后一个元素的位置设为end,把将要排序的数据的位置设为end+1,我们要将这个待插入的数据保存起来,因为在比较的过程中可能会造成数据的后移,那这个数就会被覆盖了

接着将待插入的数据与有序区间的最后一个数进行比较,如果待插入的数据比arr[end]小,那么让arr[end]后移,然后arr[end]前面的数据又组成了一个新的有序区间,让新区间最后一个位置继承end,继续往前比较,那么我们就要将这段逻辑放在一个循环里。这个循环什么时候结束呢?就是当这个【end < 0】为止

若是在中途比较的过程中发现有比待插入数据还要小或者相等的数,就停止比较,跳出这个循环。因为随着有序区间中数的后移,end后一定会空出一个位置,此时呢执行a[end + 1] = tmp;就可以将这个待插入数据完整地放入有序区中并且使这个有序区依旧保持有序

代码实现 

// 定义变量 end,表示当前元素的前一个位置
int end;
// 将待插入元素保存在临时变量 temp 中
int temp = arr[end + 1];
// 在当前元素之前的已排序部分进行插入排序,直到找到适当位置插入 temp
while (end >= 0)
{
    // 如果待插入元素小于当前元素,则将当前元素向右移动一位
    if (temp < arr[end])
    {
        arr[end + 1] = arr[end]; // 向右移动当前元素
        end--; // 继续向左移动
    }
    else
    {
        break; // 如果待插入元素大于等于当前元素,则说明找到了插入位置,跳出循环
    }
}
arr[end + 1] = temp; // 将 temp 插入到正确的位置

2. 高手村 - 无序区间的循环排序

读到这相信新手村的代码已经能熟练掌握了,那么接下来我们去往高手村领教更厉害的代码吧

注意:循环的结束条件是:i < n - 1,而不是 i < n,若是写成i < n,那么end最后为n-1,而temp = end +1也即temp=arr[n],数组的下标只能到n-1,现在要去访问下标n会造成越界访问,那么这个位置就会出现一个随机值

代码实现

void InsertSort(int* a, int n)
{
    // 不可以用 < n,否则在最后的迭代中,tmp会尝试访问 a[n],造成越界。
    for (int i = 0; i < n - 1; ++i)
    {
        int end = i;
        int tmp = a[end + 1]; // 将end后一个位置的元素保存起来,这是待插入的元素

        // 进行内部的while循环来找到tmp应该插入的位置
        while (end >= 0)
        {
            if (tmp < a[end]) // 如果tmp小于当前位置的元素
            {
                a[end + 1] = a[end]; // 将当前元素后移
                end--; // 向前移动end
            }
            else
            {
                // 如果找到了一个不大于tmp的元素,停止移动
                break;
            }
        }
        // 在正确的位置插入tmp
        a[end + 1] = tmp;
    }
}

三. 时间复杂度和空间复杂度

时间复杂度

  1. 最好情况(Best Case):

    • 情景:当输入数组已经是有序的,每次比较仅发生一次即可确认插入位置。
    • 计算:对于每个元素(从第二个开始),我们比较一次,然后插入,总共进行 𝑛−1n−1 次操作。
    • 时间复杂度:O(n)
  2. 最坏情况(Worst Case):

    • 情景:当输入数组完全逆序,每次插入的元素都必须比较到最前面才能找到插入位置。
    • 计算
      • 第一个元素认为已排序。
      • 第二个元素在最坏的情况下需要比较1次。
      • 第三个元素需要比较2次。
      • ...
      • 第 𝑛n 个元素需要比较 𝑛−1n−1 次。
      • 因此,总的比较次数是 1+2+⋯+(𝑛−1)=(𝑛−1)𝑛21+2+⋯+(n−1)=2(n−1)n​,近似于 𝑂(𝑛2)O(n2)。
    • 时间复杂度:O(n²)
  3. 平均情况(Average Case):

    • 情景:考虑所有可能的数组排列,平均每个元素需要比较一半的已排序序列长度。
    • 计算
      • 平均比较次数与移动次数也是近似于二次函数,类似于最坏情况的计算。
    • 时间复杂度:O(n²)

空间复杂度

  • 直接插入排序是一种原地排序算法。
  • 空间复杂度为 O(1),因为除了输入数组外,只需要一个额外的临时变量用于存放当前要插入的值

四. 总结

直接插入排序在最好情况下非常高效,适用于数据已经部分排序的场景。然而,在最坏和平均情况下,它的效率较低,特别是对于大规模数据。其主要优点是实现简单,排序稳定,且在数据规模较小或基本有序的情况下表现良好。