题外话:感冒了,鸽了几天,满血复活!
1.什么是插入排序?
- 想象你在打扑克牌,现在正在摸牌阶段;
- 你摸到了一张7,为了打牌更加舒适,我们通常会选择把手里的牌按一定顺序排序,比如从小到大;
- 那么这张7,在排序后,需要插入到5和10之间;
- 你不断的摸牌,不断的将新的牌,插入到手牌中,来保证正确的顺序;
- 这个过程,就是插入排序。
2.直接插入排序
-
什么是直接插入排序?
- 和上面的例子一样,拿一张,插一张;
- 而且,在找插入位置的时候,你选择一张一张牌进行大小比较;
- 这个流程,就是直接插入排序。
-
代码实现
/// <summary> /// 直接插入排序 /// </summary> /// <param name="pArray">索引从1开始有效的数组</param> static public void DirectSort(int[] pArray) { int i, j; for (i = 1; i < pArray.Length; i++) { //设置哨兵 pArray[0] = pArray[i]; //寻找插入点的同时,向后移动元素 for (j = i - 1; pArray[j] > pArray[0]; j--) pArray[j + 1] = pArray[j]; //插入元素 pArray[j + 1] = pArray[0]; } }
-
时间复杂度
- 最好的情况:O(n)
- 最差的情况:O(n^2)
- 平均:O(n^2)
-
特点:
- 原始数据越有序,直接插排的性能越高,越接近O(n)。
- PS: 此处的“有序”,指的是和排序后的结果同向,比如说都是从小到大or都是从大到小。
-
空间复杂度
- O(1)
-
算法稳定性
- 稳定
3.二分插入排序
-
什么是二分插入排序?
- 和直接插排类似,都是一张牌一张牌插入到手牌中;
- 但是,在查找插入位置时,使用二分查找来确定插入位置。
-
代码实现
/// <summary> /// 二分插排 /// </summary> /// <param name="pArray">索引从1开始有效的数组</param> static public void BinarySort(int[] pArray) { int low, high, mid; for (int i = 1; i < pArray.Length; i++) { //设置哨兵 pArray[0] = pArray[i]; //二分查找双指针设置 low = 1; high = i - 1; //二分查找 while (low <= high) { mid = (low + high) / 2; if (pArray[mid] > pArray[0]) { high--; } else { low++; } } //逐位向后移动 for (int j = i; j > high + 1; j--) { pArray[j] = pArray[j - 1]; } //high + 1是需要插入的位置 pArray[high + 1] = pArray[0]; } }
-
时间复杂度
- 最好的情况:O(n^2)
- 最差的情况:O(n^2)
- 虽然量级和直接插排一样,单就平均性能而言,要更加优秀。
-
空间复杂度
- O(1)
-
算法稳定性
- 稳定
4.希尔排序
-
为什么有希尔排序?
- 在“直接插排”中,我们讲到“原始数据越有序,性能越高”;
- 顺着这个思路,“我们可不可以在排序前,让数据大致有序,从而提高性能呢?”
- 诶!!希尔排序他来了,就是这个思路。
-
希尔排序的思路
- 我们设定一个步长,比如说5,间隔为5的元素之间,进行直接插排,比如说1,6,11三个元素排序,2,7,12三个元素排序,以此类推。
- 我们再设定一个步长,3,间隔为3的元素之间,进行插排;
- 最后,我们设定步长为1,就是普通的直接插排;
- 得到最终结果。
-
关于步长的设定
-
代码实现
/// <summary> /// 希尔排序 /// </summary> /// <param name="pArray">索引从1开始有效的数组</param> /// <param name="delta">互质递减序列</param> static public void ShellSort(int[] pArray, int[] delta) { //对互质递减序列中,每个step进行一次排序 for (int i = 0; i < delta.Length; i++) { ShellInsert(pArray, delta[i]); } } static public void ShellInsert(int[] pArray, int step) { int i, j; //从step + 1开始,一个元素一个元素进行普通的直接插排 //为什么从step + 1开始?从1-step的这step个元素,并不需要走一遍循环,因为它们自身所在的排序序列中,只有他们自己 //而从step + 1开始,可以照顾到所有的元素会进行插排,且保证每个元素前面都至少有一个元素可以进行比较。 for (i = step + 1; i < pArray.Length; i++) { //设置哨兵 pArray[0] = pArray[i]; //寻找插入点的同时,向后移动元素 for (j = i - step; j > 0 && pArray[j] > pArray[0]; j = j - step) pArray[j + step] = pArray[j]; pArray[j + step] = pArray[0]; } } }
-
时间复杂度
- 最好的情况:O(n)
- 最差的情况:O(n^2)
- 平均:O(n^1.3)
-
空间复杂度
- O(1)
-
算法稳定性
- 不稳定
结束语: 这些算法,看着挺麻烦,写完了之后发现还是蛮简单的,哈哈哈