下面是一个典型的插入排序的示意图:
从上面的示意图可以看出来,插入排序的原理是从第2个元素开始,依次与前面的元素比较大小,从而找到该元素的正确位置并插入该位置。通过循环遍历,找到每一个元素的正确位置并插入,就完成了排序。这类似于打牌的时候,把每一张抓到的牌根据牌面大小插入正确的位置。
典型的插入排序的代码如下:
public static void insertionSort(int[] arr) {
for (int i = 1; i < arr.length; i++) {
for (int j = i; j > 0; j--) {
if (arr[j] >= arr[j - 1]) {
break;
}
int temp = arr[j - 1];
arr[j - 1] = arr[j];
arr[j] = temp;
}
}
}
上面的示意图以及代码中,寻找第i个元素的正确位置时,采用的是从后向前遍历的方式,即将arr[i]依次与arr[i-1]、arr[i-2] ... 比较,直到找到正确的位置。
然而,由于arr[i]前面的所有元素已经是排过序的,而在有序数列中寻找指定大小的元素,二分查找的效率高于遍历查找。因此可以通过二分查找的方式找到其正确位置。其原理是找到第0~i-1个元素的中间元素arr[mid],然后比较arr[i]与arr[mid]的大小。如果arr[i]大于arr[mid],则从arr[mid]~arr[i-1]中继续寻找。以此类推,直到找到正确位置。
优化后的代码如下:
public static void insertionSortImproved(int[] arr) {
for (int i = 1; i < arr.length; i++) {
int left = 0;
int right = i;
while (left <= right) {
int mid = (left + right) / 2;
if (arr[i] > arr[mid]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
int insertIndex = left;
int insertValue = arr[i];
// 移动元素腾出插入位置
for (int j = i; j > insertIndex; j--) {
arr[j] = arr[j - 1];
}
// 插入新元素
arr[insertIndex] = insertValue;
}
}
经测试,当排序数组的元素为万级别时,优化后的插入排序的速度大约为原来的4倍。