插入排序

插入排序

简介

插入排序类似于扑克牌的排序

在这里插入图片描述

  • 执行流程
    • 在执行过程中,插入排序会将序列分为2部分, 头部是已经排好序的,尾部是待排序的
      在这里插入图片描述
    • 从头开始扫描每一个元素,每当扫描到一个元素,就将它插入到头部合适的位置,使得头部数据依然保持有序
图示

代码
// array 用于排序的数组
for (int begin = 1; begin < array.length; begin++) {
    int cur = begin;  // 用于保存当前需要插入的数据
    // 交换数据
    while (cur > 0 && cmp(cur, cur - 1) < 0) {
        swap(cur, cur - 1);
        cur--;
    }
}

// cmp 方法用于比较下标数的大小
// swap 方法用于交换数组中两个下标之间的数据
插入排序–逆序对(Inversion)
  • 定义

数组<2, 3, 8, 6, 1>的逆序对为:<2, 1>, < 3, 1>, <8, 1>, <6, 1>, 共5个逆序对

插入排序的时间复杂度与逆序对的数量成正比关系,逆序对数量越多,插入排序的时间复杂度越高

直观的理解就是插入时,我们需要交换元素,直到目标元素插入到了对应的位置,而逆序对的个数可以只管的看到大小关系(是否该交换)—感觉是废话

分析算法
  • 最坏,平均时间复杂度: O(n^2)

在这里插入图片描述
对于每一个元素需要插入的位置都在数组头部,导致交换数量最大

  • 最好时间复杂度: O(n)

已经排好序了

  • 空间复杂度: O(1)
  • 属于稳定排序

当逆序对的数量极少时, 插入排序的效率特别高,甚至速度比O(nlgn)级别的快速排序还要快

数据量不是特别大的时候,插入排序的效率也是非常好的

优化

但是该方法还可以优化,我们使用的是交换数据的方式去达到是目标元素插入到对应的位置,二交换元素的代码一般是三行

void swap (int num1, num2) {
    int temp = num2;
    num2 = num1;
    num1 = temp;
}
  • 优化思路

交换转为挪动
先将待插入的元素备份
头部有序数据中比待插入元素大的,都朝尾部方向挪动1个位置
将待插入元素放到最终的合适位置

在这里插入图片描述

  • 代码
    for (int begin = 1; begin < array.length; begin++) {
        int cur = begin;
        T v = array[cur];
        while (cur > 0 && cmp(v, array[cur - 1]) < 0) {
            array[cur] = array[cur - 1];
            cur--;
        }
        array[cur] = v;
    }

// cmp 方法用于比较下标数的大小
// swap 方法用于交换数组中两个下标之间的数据
进一步优化

在第一种方式的基础上,我们对移动元素进行了优化,那可不可以减少比较的次数呢?

  • 二分搜索

如何确定一个元素在数组中的位置?

如果是无序数组,从第0个位置开始遍历搜索,平均时间复杂度:O(n)

如果是有序数组,可以使用二分搜索,最坏时间复杂度:O(logn)

  • 二分搜索思路

假设在[begin, end)范围内搜索某个元素vmid == (begin + end)/2

如果v < m,去[begin, end)范围内二分搜索

如果v > m, 去[begin, end)范围内二分搜索

如果v == m, 直接返回mid

0067

注意:我们使用的是左闭右开,所以上图的end位置可以向右移一位,这样做的好处是可以直接使用end - begin得到数组的长度

  • 二分实例

0068

  • 二分代码
public static int search(int[] array, int n) {
    if (array == null || array.length == 0) return -1;
    int begin = 0, end = array.length;
    while (begin < end) {
        int mid = (begin + end) >> 1;
        if (n == array[mid]) {
            end = mid;
        }
        else if (n > array[mid]) {
            begin = mid + 1;
        }
        else {
            return mid;
        }
    }
    return -1;
}

如果存在多个重复的值的时候,我们返回的值得位置是不确定的没有特殊性

  • 二分搜索优化

在进行插入排序的时候,我们可以先二分搜索出合适的插入位置,然后将元素插入,相当于减少了寻找插入位置的比较次数,但是元素移动是不变的,通过搬运

现在需要解决的问题是如何找到该插入的位置(通过二分搜索确定)?

搜索5

00610

搜索1

搜索15

在这里插入图片描述

总结: 小于就去左边,大于去右边,等于时我们需要也是右边

  • 二分搜索优化-代码实现
// 主函数体
for (int i = 1; i < array.length; i++) {
    // 二分搜索寻找插入位置,然后移动元素
    insert(i, search(i));
}

// search
private int search(int index) {
    int begin = 0;
    int end = index;
    while (begin < end) {
        int mid = (begin + end) >> 1;
        if (cmp(index, mid) < 0) {
            end = mid;
        }
        else {
            begin = mid + 1;
        }
    }
    return begin;
}

paivate void insert(int source, int dest) {
    T v = array[source];
    // 移动元素
    for (int i = source; i > dest; i--) {
        array[i] = array[i - 1];
    }
    // 插入到相应的位置
    array[dest] = v;
}

注意: 使用了二分搜索后,只是减少了比较次数,但是插入排序的平均时间复杂度依然是O(n^n)

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值