概念:
插入排序算法的核心是,把一个原本序列分成有序区和无序区,然后通过比较和后移把无序区的元素插入有序区内。
把一个原本序列分成有序区和无序区,如下图所示:
然后通过不断的比较和后移把无序区的元素插入有序区内,为了更直观的展示这一过程,如下动画。
- 将原序列分成有序区和无序区。a[0…i-1]为有序区,a[i…n] 为无序区。(i从1开始)
- 从无序区中取出第一个元素,即a[i],在有序区序列中从后向前扫描。
- 如果有序元素大于a[i],将有序元素后移到下一位置。有序元原本的位置赋值为a[i]
- 重复步骤3,直到找到小于或者等于a[i]的有序元素.
- 重复步骤2~4,直到无序区元素为0。
代码如下:
1 | fun <T : Comparable<T>> MutableList<T>.insertSort(): MutableList<T> { |
这个解法有个可以优化的点,就是它会不断的发生元素的交换,为此可以改进为当有序元素大于a[i],只将有序元素后移到下一位。然后重复这步骤,直到找到小于或等于a[i]的有序元素,再将a[i]插入到该有序元素的下一个位置中。
- 将原序列分成有序区和无序区。a[0…i-1]为有序区,a[i…n] 为无序区。(i从1开始)
- 从无序区中取出第一个元素,即a[i],在有序区序列中从后向前扫描。
- 如果有序元素大于a[i],将有序元素后移到下一位置。
- 重复步骤3,直到找到小于或者等于a[i]的有序元素,将a[i]插入到该有序元素的下一位置中。
- 重复步骤2~4,直到无序区元素为0。

实现优化后的插入排序的直接插入排序(语言kotlin),代码如下:
1 | /** |
时间复杂度:
直接插入排序的耗时操作有:比较和后移赋值。
最好的情况:原有的序列是顺序,比较的次数是n-1次,后移赋值的操作是0次,时间复杂度即即O(n)
最坏的情况:原有的序列是降序,比较的次数是n*(n-1)/2次,后移赋值的操作次数比较的次数再加上n-1次。即O(n^2)
二分查找插入排序
概念:
二分查找插入排序的原理:是直接插入排序的一个变种,区别是:在有序区中查找新元素插入位置时,为了减少元素比较次数提高效率,采用二分查找算法进行插入位置的确定。
具体如下实现为升:假设数组为a[0…n]。
将原序列分成有序区和无序区。a[0…i-1]为有序区,a[i…n] 为无序区。(i从1开始)
从无序区中取出第一个元素,即a[i],使用二分查找算法在有序区中查找要插入的位置索引j。
将a[j]到a[i-1]的元素后移,并将a[i]赋值给a[j]。
重复步骤2~3,直到无序区元素为0。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40/**
* 二分插入排序,是对直接插入排序的一个改进。
* 因为在有序区找到一个插入的位置,可以使用二分查找,减少元素比较的次数提高效率
*/
fun <T : Comparable<T>> MutableList<T>.binaryInsertSort(): MutableList<T> {
for (i in 1 until size) {
var j = i - 1
val temp = get(i)
val index = this.binarySearch(temp, fromIndex = 0, toIndex = j)
while (j >= 0 && j > index) {
set(j + 1, get(j))
j--
}
set(index, temp)
}
return this
}
/**
* 二分查找
*/
fun <T : Comparable<T>> List<T>.binarySearch(
element: T,
fromIndex: Int = 0,
toIndex: Int = size,
): Int {
var low = fromIndex
var high = toIndex - 1
while (low <= high) {
val mid = (low + high) / 2// safe from overflows
val midVal = get(mid)
if (midVal > element) {
high = mid - 1
} else {
low = mid + 1
}
}
return low
}时间复杂度:O(n^2)
二分查找插入位置,因为不是查找相等值,而是基于比较查插入合适的位置,所以必须查到最后一个元素才知道插入位置。
二分查找最坏时间复杂度:当2^X>=n时,查询结束,所以查询的次数就为x,而x等于log2n(以2为底,n的对数)。即O(log2n)所以,二分查找排序比较次数为:x=log2n
二分查找插入排序耗时的操作有:比较 + 后移赋值。时间复杂度如下:最好情况:查找的位置是有序区的最后一位后面一位,则无须进行后移赋值操作,其比较次数为:log2n 。即O(log2n)
最坏情况:查找的位置是有序区的第一个位置,则需要的比较次数为:log2n,需要的赋值操作次数为n(n-1)/2加上 (n-1) 次。即O(n^2)
渐进时间复杂度(平均时间复杂度):O(n^2)
空间复杂度:O(1)
从实现原理可知,二分查找插入排序是在原输入数组上进行后移赋值操作的(称“就地排序”),所需开辟的辅助空间跟输入数组规模无关,所以空间复杂度为:O(1)希尔排序
维基百科
希尔排序也称递减增量排序算法,是插入排序的一种更高效的改进版本。
- 如果一个较小的数据在已排好序的数组的末端,要让这个数据放到正确的位置。如果使用直接插入排序,需要进行n次的比较和交换才能。因为在插入排序中每次只能将数据移动一位。
1 | /** |