插入排序
[toc]
把数字插入合适的位置
1.执行流程
- 在执行过程中,插入排序会讲序列分为两部分
- 头部是已经排好序的,尾部是待排序的
- 从头开始扫描每一个元素
- 每当扫描到一个原生,就将它插入到头部合适的位置,使得头部数据依然保持有序
2.dart代码
import 'sort.dart';
class InsertSort > extends Sort{
@override
sort() {
for (var begin = 1; begin < list.length; begin++) {
int cur = begin;
while (cur >0 &&cmpWithIndex(cur, cur-1)<0) {
swap(cur, cur-1);
cur -= 1;
}
}
}
}
2.1校验
main(List args) {
// List list = IntergerTools.random(10000, 1, 20000); //生成10000个数,最小是1,最大是20000
List list = IntergerTools.random(10, 1, 20); //生成10000个数,最小是1,最大是20000
List sortList = [
// HeapSort(),
// SelectSort(),
// BubbleSort2(),
// BubbleSort1(),
// BubbleSort(),
InsertSort(),
// InsertSort1()
];
testSorts(list, sortList);
}
void testSorts(List list, List sorts) {
print('排序前 :$list');
for (Sort sort in sorts) {
List newList = List.from(list);
sort.setList(newList);
sort.sortPrint();
Asserts.test(IntergerTools.isAscOrder(sort.list));
print('排序后 :${sort.list}');
}
sorts.sort(); //比较
for (Sort sort in sorts) {
print(sort);
}
}
结果
排序前 :[11, 19, 18, 15, 5, 5, 13, 10, 14, 17]
排序后 :[5, 5, 10, 11, 13, 14, 15, 17, 18, 19]
【InsertSort】
耗时:0.001s (1ms) 比较:41 交换:24
-----------------
3. 优化1
思路是将[交换]转为[挪动]
- /// 先将待插入的元素备份
- /// 头部有序数据中比待插入元素大的,都朝尾部方向挪动1个位置
- /// 将待插入元素放到最终的合适位置
3.1代码
///
/// Author: liyanjun
/// Date: 2020-11-01 16:13:16
/// FilePath: /algorithm/sort/insert_sort1.dart
/// Description: 插入排序的优化1:将交换改为挪动
/// 思路是将[交换]转为[挪动]
/// 先将待插入的元素备份
/// 头部有序数据中比待插入元素大的,都朝尾部方向挪动1个位置
/// 将待插入元素放到最终的合适位置
///
import 'sort.dart';
class InsertSort1> extends Sort{
@override
sort() {
for (var begin = 1; begin < list.length; begin++) {
int cur = begin;
T v = list[cur];
while (cur >0 &&cmpElement(v, list[cur-1])<0) {
list[cur] = list[cur-1];
cur -= 1;
}
list[cur] = v;
}
}
}
3.2 校验
main(List args) {
// List list = IntergerTools.random(10000, 1, 20000); //生成10000个数,最小是1,最大是20000
List list = IntergerTools.random(10, 1, 20); //生成10000个数,最小是1,最大是20000
List sortList = [
// HeapSort(),
// SelectSort(),
// BubbleSort2(),
// BubbleSort1(),
// BubbleSort(),
// InsertSort(),
InsertSort1()
];
testSorts(list, sortList);
}
void testSorts(List list, List sorts) {
print('排序前 :$list');
for (Sort sort in sorts) {
List newList = List.from(list);
sort.setList(newList);
sort.sortPrint();
Asserts.test(IntergerTools.isAscOrder(sort.list));
print('排序后 :${sort.list}');
}
sorts.sort(); //比较
for (Sort sort in sorts) {
print(sort);
}
}
结果
排序前 :[18, 18, 8, 16, 1, 16, 7, 16, 9, 2]
排序后 :[1, 2, 7, 8, 9, 16, 16, 16, 18, 18]
【InsertSort1】
耗时:0.001s (1ms) 比较:46 交换:0
-----------------
4. 优化2
利用二分查找优化
- 如果是一个无需数组,从第0个位置开始遍历搜索,平均时间复杂度:O(n)
- 如果是有序数组,可以使用二分搜索,最坏时间复杂度:O(logn)
4.1 思路
二分的思路
- 假设在[begin, end)范围内搜索某个元素v, mid == (begin + end)/2
- 如果v < m,去[begin, mid)范围内二分搜索
- 如果v>m,去[mid+1,end)范围内二分搜索
- 如果v== m,直接返回mid
插入的思路
- 在元素v的插入过程中,可以先二分搜索出合适的插入位置,然后再将元素V插入
- 然后挪动运输
4.2 代码
import 'sort.dart';
class InsertSort2> extends Sort{
@override
sort() {
for (var begin = 1; begin < list.length; begin++) {
_insert(begin, _search(begin));
}
}
/**
* 将source位置的元素插入到dest位置
* @param source
* @param dest
*/
_insert(int source,int dest){
T v = list[source];
for (int i = source; i > dest; i--) {
list[i] = list[i - 1];
}
list[dest] = v;
}
/**
* 利用二分搜索找到 index 位置元素的待插入位置
* 已经排好序数组的区间范围是 [0, index)
* @param index
* @return
*/
int _search(int index) {
int begin = 0;
int end = index;
while (begin < end) {
int mid = (begin + end) >> 1;
if (cmpWithIndex(index, mid) < 0) {
end = mid;
} else {
begin = mid + 1;
}
}
return begin;
}
}
4.3 校验
main(List args) {
// List list = IntergerTools.random(10000, 1, 20000); //生成10000个数,最小是1,最大是20000
List list = IntergerTools.random(10, 1, 20); //生成10000个数,最小是1,最大是20000
List sortList = [
// HeapSort(),
// SelectSort(),
// BubbleSort2(),
// BubbleSort1(),
// BubbleSort(),
// InsertSort(),
// InsertSort1(),
InsertSort2()
];
testSorts(list, sortList);
}
void testSorts(List list, List sorts) {
print('排序前 :$list');
for (Sort sort in sorts) {
List newList = List.from(list);
sort.setList(newList);
sort.sortPrint();
Asserts.test(IntergerTools.isAscOrder(sort.list));
print('排序后 :${sort.list}');
}
sorts.sort(); //比较
for (Sort sort in sorts) {
print(sort);
}
}
结果
排序前 :[16, 15, 10, 3, 7, 18, 2, 18, 3, 6]
排序后 :[2, 3, 3, 6, 7, 10, 15, 16, 18, 18]
【InsertSort2】
耗时:0.001s (1ms) 比较:41 交换:0
-----------------
4.4 注意
需要注意的是,使用了二分搜索后,只是减少了比较次数,但插入排序的平均时间复杂度依然是0(n^2)
5.逆序对
设 A 为一个有 n 个数字的有序集 (n>1),其中所有数字各不相同。
如果存在正整数 i, j 使得 1 ≤ i < j ≤ n 而且 A[i] > A[j],则 这个有序对称为 A 的一个逆序对,也称作逆序数
数组(3,1,4,5,2)的逆序对有(3,1),(3,2),(4,2),(5,2),共4个。
插入排序的时间复杂度与逆序对的数量成正比关系
逆序对的数量越多,插入排序的时间复杂度越高
- 最坏的、平均时间复杂度:O(n^2)
- 最好时间复杂度:O(n)
- 空间复杂度:O(1)
- 属于稳定排序
当逆序对的数量极少时,插入排序的效率特别高,甚至速度比O(nlogn)级别的还要快
建议:数据量不是很大的时候,插入排序的效率也是非常好的