工作原理:
每次指定一个待排序的元素,插入到前面已经排序号的序列中去,直到插完所有元素
所以,每次只需要比较指定元素和其左侧的元素大小,交换一定次数即可达到左侧的有序
本文提供三种实现方式:简单实现,减少交换次数的优化,加上哨兵位后的算法。并且在最后面测试这三中算法的效率。
简单版代码实现:
public class SortMain {
public static void main(String[] args) {
Integer[] testArray={1,4,5,2,3,6,3};
selectedSort(testArray);
for (int i:testArray){
System.out.print(i+" ");
}
System.out.println(isSorted(testArray));
}
public static boolean isSorted(Comparable[] a){
//测试是否有序,注意防止数组下标越界
for (int i=1;i<a.length;i++){
if (less(a[i],a[i-1])) return false;
}
return true;
}
//若a<b 返回true
public static boolean less(Comparable a,Comparable b){
return a.compareTo(b)<0;
}
public static void exch(Comparable[] a,int i,int min){
Comparable x;
x=a[i];
a[i]=a[min];
a[min]=x;
}
public static void insertSort(Comparable[] a){
//将a升序排列
int N=a.length;
for (int i=1;i<N;i++){
//比较元素与其左侧元素的大小,若当前元素比左侧元素小,则交换位置
for (int j=i;j>0&&less(a[j],a[j-1]);j--){
exch(a,j,j-1);
}
}
}
}
算法分析:
直接插入排序对于实际应用中常见的某些类型的非随机数组很有效,适合部份有序的小规模数组
算法优化:
前面的算法,需要在每次比较大小后交换位置。我们可以优化为在每次比较后,较大元素右移,在内循环退出后把a[i]赋值到他应该去的位置,而不总是交换两个元素,可以使访问数组的次数减半
减少交换次数优化后代码:
public static void insertSortYouHua(Comparable[] a){
//将a升序排列
int N=a.length;
for (int i=1;i<N;i++){
Comparable temp=a[i];//保存第i位的值
int j;
//设置变量记录第i位前一位,把a[i]与前面的进行比较
//若比a[i]大,则后移
//退出循环时,把a[i]的值赋值给空的位置(退出循环时又"-"了一个)
for (j=i-1;j>0&&less(temp,a[j]);j--){
a[j+1]=a[j];
}
a[j+1]=temp;
}
}
算法中加入哨兵位:
看书才知道原来有个东西叫哨兵位(原谅我是个菜鸡),是一个最大或者最小值,一般处于开头或者末尾,来避免数组下标越界的问题。有哨兵的算法和上面的算法的最大区别就在于for循环中条件的判断,测试循环条件会减少一半的时间。但是需要首先遍历一遍数组来找到哨兵(最大或者最小的值)
public static void insertSortShaoBing(Comparable[] a) {
/*
使用哨兵位的直接插入排序
*/
//将a升序排列
int N = a.length;
//为了避免数组本来就排列好,先进行判断
if (!isSorted(a)) {
//从后往前遍历,找出最小的值,放到a[0]当哨兵位
for (int i = N - 1; i > 0; i--) {
if (less(a[i], a[i - 1])) {
exch(a, i, i - 1);
}
}
}
for (int i = 2; i < N; i++) {
//内循环中直接从a[1]和a[2]的比较开始
//如果前面的值比后面的值小,就把前面的值复制到后面的位置上去
Comparable temp = a[i];//保存第i位的值
int j;
//设置变量记录第i位前一位,把a[i]与前面的进行比较
//若比a[i]大,则后移
//退出循环时,把a[i]的值赋值给空的位置(退出循环时又"-"了一个)
for (j = i - 1; less(temp, a[j]); j--) {
a[j + 1] = a[j];
}
a[j + 1] = temp;
}
}
最后进行算法的比较(三个算法处理T个长度为N的数组):
1.T=100,N=1000
public class SortCompare {
public static long time(String alg, Double[] a){
long startTime = System.currentTimeMillis(); //获取开始时间
if (alg.equals("Insertion")) SortMain.insertSort(a);
if (alg.equals("Insertion2")) SortMain.insertSortYouHua(a);
if (alg.equals("Insertion3")) SortMain.insertSortShaoBing(a);
long endTime = System.currentTimeMillis(); //获取结束时间
return endTime-startTime;
}
public static long timeRandomInput(String alg,int N,int T){
//使用算法alg将T个长度为N的数组排序
long total=0;
Double[] a=new Double[N];
Random random=new Random();
for (int t=0;t<T;t++){
//进行一次测试(生成一个数组并排序)
for (int i=0;i<N;i++){
a[i]= random.nextDouble();//生成随机数
}
total+=time(alg,a);
}
return total;
}
public static void main(String[] args) {
String alg1="Insertion";
String alg2="Insertion2";
String alg3="Insertion3";
int T=100;
int N=1000;
long t1=timeRandomInput(alg1,N,T);
long t2=timeRandomInput(alg2,N,T);
long t3=timeRandomInput(alg3,N,T);
System.out.println("Insertion is "+t1);
System.out.println("InsertionYouHua is "+t2);
System.out.println("InsertionShaoBing is "+t3);
}
}
运行多次不同结果:
2.T=1000,N=1000
运行多次不同结果:
3.T=10000,N=1000
运行多次不同结果:
总结:
简易版的直接插入算法虽然好写,但是在处理大数据时效率很低。
减少交换次数的优化对速度有很明显的提升。
加上哨兵后,速度没有变快,可能是因为要先遍历一遍数组找出最小值,这也是个大工程。以后要是要提升速度可以不写哨兵,自己注意数组下标(也可能是我哨兵用错了,欢迎指正!!!)。
我的电脑是真的有点慢........
最后给个github上大佬自己写的算法书课后习题的链接: