今天来学习数组的插入排序算法。

在讲算法之前,我们先来想象这样一个场景:我们要上体育课,体育老师要求同学们从高到矮排好队。当同学们都排好队的时候,小甲同学迟到了。这时候小甲同学随便站个地方可不行,因为这样队伍就不是排好的了,所以要将小甲同学插入到队伍中,使队伍重新又是从高到矮排好的。小甲同学对比自己和同学们的身高,找到自己的位置,排到队伍里,就完成了队伍的排序。

这个将一个无序元素插入到n个元素的有序集合,使新的(n 1)个元素的集合变成重新有序的动作就是插入排序的基本思想。借助这种思想,我们就可以把一个数组分成有序的和无序的两部分,不断将无序的部分里面的元素取出来插入到有序的部分中,使数组最终变为有序的。因为一个元素本身就有序了,我们可以把第一个元素作为最初的有序部分。

将数组从小到大排序的算法(学点算法四)(1)

排序示意图

我们来看对数组[6, 3, 9, 7, 8, 5, 0, 2, 4, 1]的从小到大排序过程:

1. 得到一个未排序数组[6, 3, 9, 7, 8, 5, 0, 2, 4, 1]。

将数组从小到大排序的算法(学点算法四)(2)

2. 得到首元素6,因为只有一个元素,所以直接放入有序部分。

将数组从小到大排序的算法(学点算法四)(3)

3. 取出下一个元素3,将其依次与有序部分的元素对比,此时有序部分元素只有6,对比后发现3小于6,所以3要作为首元素插入,得到新的有序部分[3, 6]。

将数组从小到大排序的算法(学点算法四)(4)

4. 取出下一个元素9,将其依次与有序部分的元素对比,发现9比有序部分的最后一个元素6还要大,所以将9插入到有序部分的末尾,得到新的有序部分[3, 6, 9]。

将数组从小到大排序的算法(学点算法四)(5)

5. 取出下一个元素7,将其依次与有序部分的元素对比,发现7比9小,比6大,所以将7插入到6和9之间,得到新的有序部分[3, 6, 7, 9]。

将数组从小到大排序的算法(学点算法四)(6)

6. 取出下一个元素8,将其依次与有序部分的元素对比,发现8比9小,比7大,所以将8插入到7和9之间,得到新的有序部分[3, 6, 7, 8, 9]。

将数组从小到大排序的算法(学点算法四)(7)

7. 取出下一个元素5,将其依次与有序部分的元素对比,发现5比6小,比3大,所以将5插入到3和6之间,得到新的有序部分[3, 5, 6, 7, 8, 9]。

将数组从小到大排序的算法(学点算法四)(8)

8. 取出下一个元素0,将其依次与有序部分的元素对比,发现0比有序部分的首元素3还要小,所以将0作为有序部分的首元素插入,得到新的有序部分[0, 3, 5, 6, 7, 8, 9]。

将数组从小到大排序的算法(学点算法四)(9)

9. 取出下一个元素2,将其依次与有序部分的元素对比,发现2比3小,比0大,所以将2插入到0和3之间,得到新的有序部分[0, 2, 3, 5, 6, 7, 8, 9]。

将数组从小到大排序的算法(学点算法四)(10)

10. 取出下一个元素4,将其依次与有序部分的元素对比,发现4比5小,比3大,所以将4插入到3和5之间,得到新的有序部分[0, 2, 3, 4, 5, 6, 7, 8, 9]。

将数组从小到大排序的算法(学点算法四)(11)

11. 取出最后一个元素1,将其依次与有序部分的元素对比,发现1比2小,比0大,所以将1插入到0和2之间,得到新的有序部分[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]。

将数组从小到大排序的算法(学点算法四)(12)

11. 至此,所有元素归入有序部分,排序完成。

算法实现代码如下:

/** * 数组的插入排序算法 * 从小到大排序 * * @param nums 待排序数组 * @param lo 排序区间lo索引(包含) * @param hi 排序区间hi索引(不包含) */ public static void insertionSort(int[] nums, int lo, int hi) { // 数组为null则直接返回 if (nums == null) { return; } // 索引检查 if (lo < 0 || nums.length <= lo) { throw new IllegalArgumentException("lo索引必须大于0并且小于数组长度,数组长度:" nums.length); } if (hi < 0 || nums.length < hi) { throw new IllegalArgumentException("hi索引必须大于0并且小于等于数组长度,数组长度:" nums.length); } if (hi <= lo) { // lo索引必须小于hi索引(等于也不行,因为区间是左闭右开,如果等于,区间内元素数量就为0了) throw new IllegalArgumentException("lo索引必须小于hi索引"); } if (lo 1 >= hi) { // 区间元素个数最多为1 // 无需排序 return; } for (int i = lo 1; i < hi; i ) { // 获取当前元素 int e = nums[i]; // 从当前位置的前一个元素开始,往前查找,直到索引为-1 // 或找到第一个小于等于当前元素的值(等于时可保证稳定排序) int j = i - 1; for (; j >= lo; j--) { if (nums[j] <= e) { // 如果找到一个元素小于等于当前元素 // 退出循环 break; } } // 此时j索引表示当前元素插入位置的上一个位置 // 所以插入位置索引为j 1 // j可能为-1,此方法仍然适用 int insertIdx = j 1; // 如果insertIdx == i,说明此时元素已经就位了,无需插入 if (insertIdx < i) { // 元素未就位,需要插入 // 插入方法为从insertIdx(包含)到i(不包含)之间的元素往后移一个位置, // 当前元素放到insertIdx的位置上 System.arraycopy(nums, insertIdx, nums, insertIdx 1, i - insertIdx); nums[insertIdx] = e; } } }

测试代码如下:

int[] nums = {6, 3, 9, 7, 8, 5, 0, 2, 4, 1}; System.out.println("排序前:" Arrays.toString(nums)); insertionSort(nums, 0, nums.length); System.out.println("排序后:" Arrays.toString(nums));

输出如下:

排序前:[6, 3, 9, 7, 8, 5, 0, 2, 4, 1] 排序后:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

符合我们的预期。

,