所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
内部排序:数据元素全部放在内存中的排序。
外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。
稳定性:对于相同的元素,在排序前后其相对位置保持不变。即在原序列中,r[i]=r[j],且r[i]在r[j]之前,在
排序完成后,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
七大排序根据功能型分类:插入排序: 直接插入排序(插入排序)、希尔排序
选择排序: 直接选择排序(选择排序)、堆排序
冒泡排序
快速排序
归并排序
今天,我们在这里只讨论插入排序中的两种:直接插入排序,希尔排序
插入排序相当于“抓牌”,每次将抓起的一张牌往有序的部分里插入
1.从第一个元素开始,该元素可以认为已经被排序
2.取出下一个元素,在已经排序的元素序列中从后向前扫描
3.如果该元素(已排序)大于新元素,将该元素移到下一位置
4.重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
5.将新元素插入到该位置
6.重复步骤2~5
1、最外层循环:一次让一个数有序,所以需要length 有序 [ 0,i)
无序[ i ,length)
(1) 在有序区间,为array[ i ] 找一个合适的位置(保证稳定性)
直接遍历
代码如下:
01、找和搬分开
public static void insertSort_01(int[] array){
for (int i = 0;i < array.length;i++){
/**
* 有序:[0,i)
* 无序:[i,array.length)
*/
//在有序区间内遍历查找,从后往前
int j;
for (j = i -1; j >= 0 && array[i] < array[j]; j--){
}
//j+1 就是要插入的下表
//插入数据,从后往前搬
int key = array[i];
for (int k = i; k > j +1; k--){
array[k] = array[k -1];
}
array[j +1] = key;
}
}
02、边找边搬*
//查和搬同时进行
public static void insertSort_02(int[] array){
for (int i = 0; i < array.length; i++){
int key = array[i];
int j = i -1;
for (; j >= 0 && key < array[j]; j--){
array[j +1] = array[j];
}
array[j +1] = key;
}
}
03、二分查找
如果比较操作的代价比交换操作大的话,可以采用“二分查找法”来减少比较操作的数目。该算法可以认为是插入排序的一个变种,称为二分查找插入排序。
//二分查找
public static void insertSort_03(int[] array){
for (int i = 0; i < array.length; i++){
int key = array[i];
//有序[0,i)
int left = 0;
int right = i;
while (left < right){
int mid = left + (right - left)/2;
if (key == array[mid]){
//为了追求稳定性
left = mid +1;
}else if(key < array[mid]){
//下一次查找范围:[left,mid)
right = mid;
}else{
left = mid +1;
}
}
int pos = left;
for (int k = i; k > pos; k--){
array[k] = array[k -1];
}
array[pos] = key;
}
}
插入排序总结:时间复杂度:最坏:O(n^2) 已经逆序
平均:O(n^2)
最好:O(n^2) 已经有序
空间复杂度:O(1)
稳定性:稳定
在直接插入排序算法中,每次插入一个数,使有序序列只增加1个节点,并且对插入下一个数没有提供任何帮助。如果比较相隔较远距离(称为增量)的数,使得数移动时能跨过多个元素,则进行一次比较就可能消除多个元素交换。
希尔排序是对直接插入排序的优化
当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
代码如下:
private static void insertWithGap(int[] array,int gap){
for(int i = 0;i = 0 && key < array[j]; j= j -gap){
array[j +gap] = array[j];
}
array[j +gap] = key;
}
}
public static void shellSort(int[] array){
int gap = array.length;
while(true){
//gap = gap/2;
gap = (gap/3) +1;
insertWithGap(array,gap);
if(gap == 1){
break;
}
}
}
希尔排序总结:时间复杂度:最坏:O(n^2)
平均:O(n^1.2~1.3) (不讨论怎么算出来的,知道结论就行)
最好:O(n)
空间复杂度:O(1)
稳定性:不稳定
由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。
本质上讲,希尔排序算法是直接插入排序算法的一种改进,减少了其复制的次数,速度要快很多。当gap值很大时数据项每一趟排序需要移动的个数很少,但数据项的距离很长。当gap值减小时每一趟需要移动的数据增多,此时已经接近于它们排序后的最终位置。 正是这两种情况的结合才使希尔排序效率比插入排序高很多。