插入排序有:直接插入排序、二分法插入排序、希尔排序。
排序是一种很直观的算法,用一种可视化的方法来观察算法的执行过程是很有用的,各位可以点击此链接https://visualgo.net/en/sorting来更加透彻得领悟各类排序算法。
下面是直接插入排序:
基本思想就是,p从1开始一直往后移动,每次移动(即指外层循环的每次循环)前从索引0到p-1位置都是已经排好序的,每次移动后保证从索引0到p位置都是已经排序了的,看起来好像在内层循环要不停的交换元素,实际上不需要,只需要先把元素存起来,元素一直往后复制以覆盖,最后循环结束后再赋值。这和堆的插入删除算法(即上滤下滤)是类似的。
因为有两个循环,所以算法的时间复杂度:
最坏情况:是O(n2)。
最好情况:是O(n)。最好的情况就是数据已经预先排过序了。
算法是稳定的。
public static <AnyType extends Comparable<? super AnyType>>
void insertionSort( AnyType [ ] a )
{
int j;
for( int p = 1; p < a.length; p++ )
{
AnyType tmp = a[ p ];//先将要插入的元素存起来
for( j = p; j > 0 && tmp.compareTo( a[ j - 1 ] ) < 0; j-- )//如果是<=0那么算法不稳定
a[ j ] = a[ j - 1 ];//比较后如果小,那么元素就往后移动
if(j!=p) //如果j=p,那么说明p元素就应该在p位置,不需要多余的赋值操作
a[ j ] = tmp;//出了循环,代表j已经到了该到的位置,把tmp赋值给j位置
}
}
下面是二分插入排序:
基本思想就是,p从1开始一直往后移动,每次移动(即指外层循环的每次循环)前从索引0到p-1位置都是已经排好序的,每次移动后保证从索引0到p位置都是已经排序了的。但是在寻找插入位置有点不一样了,是通过二分查找的思想来找。二分插入排序的主要操作为比较+后移赋值。
最坏情况:每次都在有序序列的起始位置插入,则整个有序序列的元素需要后移,时间复杂度为O(n2)。
最好情况:待排序数组本身就是正序的,每个元素所在位置即为它的插入位置,此时时间复杂度仅为比较时的时间复杂度,为O(log2n)。
平均情况:O(n2)。
算法是稳定的。
public static void sort(int[] a)
{
for (int i = 1; i < a.length; i++)//从一开始即可,因为只有一个元素便不用比较
{
int temp = a[i];//前把i元素存起来
int left = 0;//都是从0到i-1的位置中找到i元素的插入位置
int right = i - 1;
int mid;
while (left <= right)
{
mid = (left + right) / 2;
if (temp < a[mid])
{
right = mid - 1;
}
else
{
left = mid + 1;
}
}//退出whihe循环时,left肯定等于right+1,,而这个位置就是插入位置
for (int j = i - 1; j >= left; j--)
a[j + 1] = a[j];//元素往后移动
if(left!=i)//当i元素就应该放在i位置时,此时left就会等于i位置,但此时不需要进行插入
a[left] = temp;
}
}
下面是希尔排序:
基本思想是:先取一个小于数组长度的整数d1作为第一个增量,把文件的全部记录分组,一共分为d1组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量



希尔排序的时间复杂度与增量序列的选取有关:
如果是选取的希尔增量:
最坏情况:是O(n2)。
最好情况:是O(n)。
平均情况:
算法是不稳定的。
其余的增量序列还有Hibbard:{1, 3, 7 ..., 2^k-1},Sedgewick:{1, 5, 19, 41, 109...}该序列中的项或者是9*4^i - 9*2^i + 1或者是4^i - 3*2^i + 1。
public static <AnyType extends Comparable<? super AnyType>>
void shellsort( AnyType [ ] a )
{
int j;
for( int gap = a.length / 2; gap > 0; gap /= 2 )//增量gap最开始为长度一半,一直到gap为1
//进入最外层循环后就可以把gap当成常数
//以下两个循环其实就是插入排序
//把数组分成gap个子数组,每个子数组的插入排序,是依次一部分一部分进行的
for( int i = gap; i < a.length; i++ )//不用i=0开始,因为只有一个元素的时候不用排序
{
AnyType tmp = a[ i ];//将要插入的元素存起来
for( j = i; j >= gap && tmp.compareTo( a[ j - gap ] ) < 0; j -= gap )
//判断条件也可以写成j>0,一样效果,保证gap个数前的那个元素不是null
a[ j ] = a[ j - gap ];//元素后移
if(j!=i)//如果j=i,那么说明i位置的元素就应该在i位置,不需要多余的赋值操作
a[ j ] = tmp;
}
}