概念
插入排序(Insertion sort)是一种简单直观且稳定的排序算法。如果有一个已经有序的数据序列,要求在这个已经排好的数据序列中插入一个数,但要求插入后此数据序列仍然有序,这个时候就要用到一种新的排序方法——插入排序算法,插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的排序。
插入排序的基本思想是:每步将一个待排序的记录,按其关键码值的大小插入前面已经排序的文件中适当位置上,直到全部插入完为止。
分类
包括:直接插入排序,二分插入排序(又称折半插入排序),链表插入排序,希尔排序(又称缩小增量排序)。属于稳定排序的一种(通俗地讲,就是两个相等的数不会交换位置) 。
直接插入排序
概念
直接插入排序是一种简单的插入排序法
算法思想
- 将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
- 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
排序演示
实现代码
//第一种:
public void insertSort(int[] array){
for(int i=1;i<array.length;i++)//第0位独自作为有序数列,从第1位开始向后遍历
{
if(array[i]<array[i-1])//0~i-1位为有序,若第i位小于i-1位,继续寻位并插入,否则认为0~i位也是有序的,忽略此次循环,相当于continue
{
int temp=array[i];//保存第i位的值
int k = i - 1;
for(int j=k;j>=0 && temp<array[j];j--)//从第i-1位向前遍历并移位,直至找到小于第i位值停止
{
array[j+1]=array[j];
k--;
}
array[k+1]=temp;//插入第i位的值
}
}
}
//第二种
public static void sort1(int[] array) {
for(int i=1;i<array.length;i++) {
while(array[i]<array[i-1]){
int tar=array[i];
array[i]=array[i-1];
array[i-1]=tar;
if(i>1) {
i--;
}else {
break;
}
}
}
}
算法分析
- 时间复杂度
如果目标是把n个元素的序列升序排列,那么采用插入排序存在最好情况和最坏情况。最好情况就是,序列已经是升序排列了,在这种情况下,需要进行的比较操作需(n-1)次即可。最坏情况就是,序列是降序排列,那么此时需要进行的比较共有n(n-1)/2次。插入排序的赋值操作是比较操作的次数加上 (n-1)次。平均来说插入排序算法的时间复杂度为O(n^2)。因而,插入排序不适合对于数据量比较大的排序应用。但是,如果需要排序的数据量很小,例如,量级小于千,那么插入排序还是一个不错的选择。 - 空间复杂度
附加空间O(1) - 算法稳定性
直接插入排序是稳定的。
折半插入排序(二分插入排序)
概念
折半插入排序(binary insertion sort)是对插入排序算法的一种改进,由于排序算法过程中,就是不断的依次将元素插入前面已排好序的序列中。由于前半部分为已排好序的数列,这样我们不用按顺序依次寻找插入点,可以采用折半查找的方法来加快寻找插入点的速度。
算法思想
在将一个新元素插入已排好序的数组的过程中,寻找插入点时,将待插入区域的首元素设置为a[low],末元素设置为a[high],则轮比较时将待插入元素与a[m],其中m=(low+high)/2相比较,如果比参考元素小,则选择a[low]到a[m-1]为新的插入区域(即high=m-1),否则选择a[m+1]到a[high]为新的插入区域(即low=m+1),如此直至low<=high不成立,即将此位置之后所有元素后移一位,并将新元素插入a[high+1]。
实现代码
// 使用循环实现的二分查找
public static void binaryInsertSort(int[] array){
for(int i = 1; i < array.length; i++){//将每一个数都插入到合适的位置
int temp = array[i];
int low = 0;
int high = i - 1;
while(low <= high){//1.找到当前数应该所在的坑
int mid = (low + high) / 2;
if(temp < array[mid]){
high = mid - 1;
}else{
low = mid + 1;
}
}
for(int j = i; j >= low + 1; j--){//2.将坑到当前数之间的数进行移位
array[j] = array[j - 1];
}
array[low] = temp;//3.将当前数放入坑中
}
}
算法分析
- 时间复杂度
比直接插入算法明显减少了关键字之间比较的次数,因此速度比直接插入排序算法快,但记录移动的次数没有变,所以折半插入排序算法的时间复杂度仍然为O(n^2) - 空间复杂度
与直接插入排序算法相同。附加空间O(1) - 算法稳定性
折半插入排序算法是一种稳定的排序算法
希尔排序
概念
希尔排序,也称缩小增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
- 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;
- 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位;
算法思想
先选定一个整数d(待排序文件的总个数),把待排序文件中所有记录分成d/2个组,所有距离为d的记录分在同一组内,并对每一组内的记录进行排序。然后,取d=d/2,重复上述分组和排序的工作。当到达d =1时,所有记录在统一组内排好序。
各组内的排序通常采用直接插入法。由于开始时s的取值较大,每组内记录数较少,所以排序比较快。随着不断增大,每组内的记录数逐步增多,但由于已经按排好序,因此排序速度也比较快。
实现代码
希尔排序1:
public static void shellSort(int[] a) {
int d=a.length;
while(true){
d=d/2;
for(int x=0;x<d;x++){
for(int i=x+d;i<a.length;i=i+d){
int temp=a[i];
int j;
for(j=i-d;j>=0&&a[j]>temp;j=j-d){
a[j+d]=a[j];
}
a[j+d]=temp;
}
}
if(d==1){
break;
}
}
}
希尔排序2:两种方式一样
public static void shellSort2(int[] a) {
int d=a.length;
while(true){
d=d/2;
//1.元素将会分为d个组
for(int x=0;x<d;x++){
//2.将每个组内的元素排序:组内的元素下表的间隔为d
for(int i=x+d;i<a.length;i=i+d){
while(a[i]<a[i-d]) {
int temp=a[i];
a[i]=a[i-d];
a[i-d]=temp;
if(i-d>x) {
i=i-d;
}else {
break;
}
}
}
}
if(d==1){
break;
}
}
}
算法分析
- 时间 复杂度
O(n log n) 如果使用最佳的现在版本 - 空间复杂度
与直接插入排序算法相同。附加空间O(1) - 算法稳定性
不稳定的排序算法
场景分析
Shell排序的时间性能优于直接插入排序
希尔排序的时间性能优于直接插入排序的原因:
- 当文件初态基本有序时,直接插入排序所需的比较和移动次数均较少。
- 当n值较小时,n和 n² 的差别也较小,即直接插入排序的最好时间复杂度O(n)和最坏时间复杂度O(n²)差别不大。
- 在希尔排序开始时增量较大,分组较多,每组的记录数目少,故各组内直接插入较快,后来增量di逐渐缩小,分组数逐渐减少,而各组的记录数目逐渐增多,但由于已经按di-1作为距离排过序,使文件较接近于有序状态,所以新的一趟排序过程也较快。
因此,希尔排序在效率上较直接插入排序有较大的改进。