排序算法(1):直接插入排序

原文链接 (原文也是我写的哈,强烈推荐去原文链接看):直接插入排序 - Fucking Code

直接插入排序(Straight Insertion Sort)是一种最简单的排序方法,其基本操作是将一条记录插入到已排好的有序表中,从而得到一个新的、记录数量增 1 的有序表。^1^

一、实现思路

1.1 步骤

  • 将整个数组分组两部分,左边和右边部分;
  • 在排序的过程中,无需管右边部分的顺序,只需要保证左边始终有序;
  • 遍历从左到右,每遍历到一个新的元素,都将其取出;
  • 然后在保证顺序的左边部分中寻找其应该的位置;
  • 即,从该元素位置向左遍历,并判断是否应该插入;
  • 如不能插入,则将判断的元素向右移位,反之插入;
  • 如此反复直至遍历完成,那么整个数组都是有序的了。

1.2 流程图

注:以下面 C++ 语言的实现过程为准
True True False True False True 开始 结束 接收参数:T* arr, int n 判断:j ≥ 0 ? 判断:tmp < arr[i] ? 判断:i < n ? 定义:T tmp 定义:int j 定义:int i 右移:arr[j + 1] = arr[j] 插入:arr[j + 1] = tmp j-- i++ 赋值:j = i - 1 赋值:tmp = arr[i] 赋值:i = 1

二、实现代码

2.1 多语言版本

推荐学习 C++ 版本,更利于理解算法的本质。另外,下面 Python 的实现方式与其它三个略微有些不同,请注意。

🟣 C 17
c 复制代码
typedef int T;

void straight_insertion_sort(T* arr, const int n) {
    int j; // 当前被比较元素的索引
    for (int i = 1; i < n; i++) {
        T tmp = arr[i]; // 取出不确定位置的元素
        for (j = i - 1; j >= 0 && tmp < arr[j]; j--) // 稳定性关键点
            arr[j + 1] = arr[j]; // 右移
        arr[j + 1] = tmp; // 插入
    }
}
🔴 C++ 20
cpp 复制代码
template <typename T = int>
void straight_insertion_sort(T* arr, const int& n) {
    int j; // 当前被比较元素的索引
    for (int i = 1; i < n; i++) {
        T tmp = arr[i]; // 取出不确定位置的元素
        for (j = i - 1; j >= 0 && tmp < arr[j]; j--) // 稳定性关键点
            arr[j + 1] = arr[j]; // 右移
        arr[j + 1] = tmp; // 插入
    }
}
🔵 Python 3
python 复制代码
def straight_insertion_sort[T](lst: list[T]) -> None:
    for i in range(1, len(lst)):
        tmp = lst[i]  # 取出不确定位置的元素
        for j in range(i - 1, -1, -1):
            if tmp >= lst[j]:  # 到该插入的位置了
                lst[j + 1] = tmp  # 插入
                break
            lst[j + 1] = lst[j]  # 右移
        else:  # 此处容易忘记,遍历完了表示 tmp 是最小的,虽然但是,它还没有插入啊!
            lst[0] = tmp  # 特殊情况的插入
🟠 Java 21
java 复制代码
public static <T extends Comparable<T>> void straight_insertion_sort(T[] arr) {
    int j; // 当前被比较元素的索引
    for (int i = 1; i < arr.length; i++) {
        T tmp = arr[i]; // 取出不确定位置的元素
        for (j = i - 1; j >= 0 && tmp.compareTo(arr[j]) < 0; j--) // 稳定性关键点
            arr[j + 1] = arr[j]; // 右移
        arr[j + 1] = tmp; // 插入
    }
}
🟢 C# 12
csharp 复制代码
static void straight_insertion_sort<T>(T[] arr) where T : IComparable<T>
{
    int j; // 当前被比较元素的索引
    for (int i = 0; i < arr.Length; i++)
    {
        T tmp = arr[i]; // 取出不确定位置的元素
        for (j = i - 1; j >= 0 && tmp.CompareTo(arr[j]) < 0; j--) // 稳定性关键点
            arr[j + 1] = arr[j]; // 右移
        arr[j + 1] = tmp; // 插入
    }
}
🟡 TypeScript 5
typescript 复制代码
function straight_insertion_sort<T extends number | string>(arr: T[]): void {
    let j; // 当前被比较元素的索引
    for (let i = 1; i < arr.length; i++) {
        let tmp = arr[i]; // 取出不确定位置的元素
        for (j = i - 1; j >= 0 && tmp < arr[j]; j--) // 稳定性关键点
            arr[j + 1] = arr[j]; // 右移
        arr[j + 1] = tmp; // 插入
    }
}

2.2 测试用例

🔻输入数据
text 复制代码
9
6 28 13 72 85 39 41 6 20
🔺输出数据
text 复制代码
6 6 13 20 28 39 41 72 85

三、算法性质

3.1 时空复杂度

复杂度 😀最好情况 😭最坏情况 🫤平均情况
时间复杂度 O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2)
空间复杂度 O ( 1 ) O(1) O(1) O ( 1 ) O(1) O(1) O ( 1 ) O(1) O(1)
3.1.1 时间复杂度分析

有一个大循环从左边第 2 个元素开始到右边遍历了数组的每一个元素,即 n-1 个元素被大循环遍历,在每一个小循环中,该元素会反向对左边已遍历(排好序)的元素进行再次遍历,遍历 i-1 次。但每次小循环会在元素插入的时候终止,我们并不知道是在什么时候终止的,但我们知道,这是随机的(取决于数据)。

当数组初始为顺序时,每个小循环只需要遍历 1 次,反之,当数组逆序的时候,就需要完成全部的遍历过程,即每次小循环要遍历 i-1 次,平均下来,每次小循环遍历 (i-1)/2 次,因此时间复杂度:

数组顺序时的最好情况:

O ( T n ) = O ( 1 + 1 + 1 + . . . + 1 ) = O ( n − 1 ) = O ( n ) O(T_n) = O(1 + 1 + 1 + ... + 1) = O(n-1) = O(n) O(Tn)=O(1+1+1+...+1)=O(n−1)=O(n)

数组逆序时的最坏情况:

O ( T n ) = O ( 1 + 2 + 3 + . . . + ( n − 1 ) ) = O ( n ( n − 1 ) 2 ) = O ( n 2 ) O(T_n) = O(1 + 2 + 3 + ... + (n-1)) = O\Big(\frac{n(n-1)}{2}\Big) = O(n^2) O(Tn)=O(1+2+3+...+(n−1))=O(2n(n−1))=O(n2)

平均情况:

O ( T n ) = O ( 1 2 + 2 2 + 3 2 + . . . + n − 1 2 ) = O ( n ( n − 1 ) 4 ) = O ( n 2 ) O(T_n) = O\Big(\frac{1}{2} + \frac{2}{2} + \frac{3}{2} + ... + \frac{n-1}{2}\Big) = O\Big(\frac{n(n-1)}{4}\Big) = O(n^2) O(Tn)=O(21+22+23+...+2n−1)=O(4n(n−1))=O(n2)

3.1.2 空间复杂度分析

整个算法的过程中,我们只用了 1 个临时变量来存储被取出来的那个元素,因此空间复杂度:

O ( S n ) = O ( 1 ) O(S_n) = O(1) O(Sn)=O(1)

3.2 稳定性与排序方式

算法 稳定性 排序方式
直接插入排序 可以稳定 内部排序
3.2.1 稳定性分析

能否稳定取决于具体的实现,实现细节没把握好也可能导致不稳定。关键在于对元素比较出现相等情况时是否应该插入的判断。

3.2.2 排序方式分析

排序方式属于内部排序,没有用到外部的空间。

四、相关习题

注:习题不保证与上述算法一定相关,只是可能相关

4.1 编程题

4.2 选择题


  1. 直接插入排序 · 百度百科 ↩︎
相关推荐
xiaoshiguang38 分钟前
LeetCode:222.完全二叉树节点的数量
算法·leetcode
爱吃西瓜的小菜鸡9 分钟前
【C语言】判断回文
c语言·学习·算法
别NULL11 分钟前
机试题——疯长的草
数据结构·c++·算法
TT哇16 分钟前
*【每日一题 提高题】[蓝桥杯 2022 国 A] 选素数
java·算法·蓝桥杯
yuanbenshidiaos2 小时前
C++----------函数的调用机制
java·c++·算法
唐叔在学习2 小时前
【唐叔学算法】第21天:超越比较-计数排序、桶排序与基数排序的Java实践及性能剖析
数据结构·算法·排序算法
ALISHENGYA2 小时前
全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之分支结构(switch语句)
数据结构·算法
chengooooooo2 小时前
代码随想录训练营第二十七天| 贪心理论基础 455.分发饼干 376. 摆动序列 53. 最大子序和
算法·leetcode·职场和发展
jackiendsc2 小时前
Java的垃圾回收机制介绍、工作原理、算法及分析调优
java·开发语言·算法
游是水里的游3 小时前
【算法day20】回溯:子集与全排列问题
算法