插入排序
简介
插入排序类似于扑克牌的排序
- 执行流程
- 在执行过程中,插入排序会将序列分为2部分, 头部是已经排好序的,尾部是待排序的
- 从头开始扫描每一个元素,每当扫描到一个元素,就将它插入到头部合适的位置,使得头部数据依然保持有序
- 在执行过程中,插入排序会将序列分为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)范围内搜索某个元素v
,mid == (begin + end)/2
如果v < m,去[begin, end)
范围内二分搜索
如果v > m, 去[begin, end)
范围内二分搜索
如果v == m, 直接返回mid
注意
:我们使用的是左闭右开,所以上图的end位置可以向右移一位,这样做的好处是可以直接使用end - begin
得到数组的长度
- 二分实例
- 二分代码
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
搜索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)