算法描述
对数组 A[1...n] 进行排序时,插入排序算法从左到右遍历整个数组,挨个将未排序元素从右到左和左边已经排好序的元素进行比较,直到找到第一个不大于它的元素时,将其插入作为该元素的后继;或者遍历结束也找不到,其便是新的最小值,插入作为首元素。完成插入从而使得有序区间的规模增一。当遍历结束时整个数组变得有序。
代码(JAVA)
class InsertionSort {
/**
* 按非降序对int[]型数组arr的[low, high)区间内的元素排序,调用需保证区间合法。
* 即0 <= low <= high <= arr.length
* @param arr 待排序数组
* @param low 待排序区间的起始下标
* @param high 待排序区间的终止下标
*/
static void sort(int[] arr, int low, int high) {
//next从左到右遍历无序元素挨个插入到有序的区间中去
//right用来寻找当前插入元素在有序区间中的正确位置
for(int next = low, right = next; ++next < high; right = next) {
//若元素本来就在正确位置那么什么都不用做
if(arr[right] > arr[next]) {
int cache = arr[next];
do{
arr[right + 1] = arr[right];
} while(--right >= low && arr[right] > cache);
arr[right + 1] = cache; //由于最后一次比较失败导致错一位,这里加1补回来
}
}
}
}
性能分析
要分析插入排序算法的性能,首先需要抽象出其处理数据的数学模型。对于排序算法而言,它并不会关心自己处理的数据具体代表什么,而只关心数据之间的大小关系。所以对于任意一组数据,都可以将里面的元素抽象成其在整组数据里面的大小排名(rank)。简单起见,这里假设所有元素大小均不相同,于是任一组数据便可对应于集合
π={1,2,3,...,n}
的一种排列。
然后引入逆序对(invertion pair)的概念:
定义:在数列
A[1...n]
中若存在下标
1≤i<j≤n
使得
A[i]>A[j]
,则称
(A[i],A[j])
为一个逆序对。数列中逆序对的总数为逆序数
显然数组的逆序数能很好地描述该数组的无序程度。而且不难看出的是一组数据的逆序数刚好就是插入排序需要执行的比较移动操纵的次数。因为在进行元素的插入时,每一次比较出元素该再往前一步时,随着较大的元素后移一步恰好就修复了一个逆序对。
- 最好情况:当数组本身有序时,逆序数为0,时间复杂度为 Θ(n)
- 最坏情况:当数组本身逆序时,逆序数为 ∑ni=1(i−1)=n(n−1)2 ,时间复杂度为 Θ(n2)
- 平均情况:
设随机变量
X
为输入排列的逆序数,即
并且令随机变量
则
所以逆序数的期望为
所以插入排序的 期望时间复杂度也为 Θ(n2) ,虽然和最坏情况同阶,不过常系数约为它的一半。
实验数据
总结
- 对于近乎有序的数据而言插入排序算法十分高效。
- 对于规模很小的数据插入排序也是极佳的选择,由于其所进行的操作很简单所以平均每次操作的开销都很小。这一点使得像快速排序以及归并排序等算法都用插入排序来处理自己划分出来的小区间以优化性能。
- 插入排序是稳定的排序算法,即不改变相同大小元素的原始次序关系。
链接
排序算法可视化网站:Sorting Algorithm Animations