插入排序练习,写第一遍的时候出了个bug,写第二遍修复了bug,写第三遍对整个流程进行了优化。
第一遍:
package sort;
//直接插入排序
public class StraightInsertionSort {
public static void main(String[] args) {
int[] arr = {12,32,3,45,78,43,53,24,67,45};
int inserted; //临时变量
//错误案例
for(int x=1;x<arr.length;x++){ //外层循环,从第二个元素arr[1]开始,到最后一个元素
inserted = arr[x]; //将第二个元素arr[1]的值赋给临时变量
for(int y=x-1;y>=0;y--){
if(y==0 && arr[y]>inserted){
arr[y+1] = arr[y];
arr[y] = inserted; //处理完边界情况后,arr[0]和arr[1]中的值已经排序正确
}
if(arr[y]>inserted){ //!!!出错位置:arr[0]>inserteg结果为false
arr[y+1] = arr[y];
}else{
arr[y+1] = inserted; //执行,将inserted中的最小值赋给了arr[1]
break; //所以结果arr[0]和arr[1]中的值一样
}
}
}
for(int i=0;i<arr.length;i++){
System.out.print(arr[i]+",");
}
}
}
上述代码的运行结果为:
3,3,24,32,43,45,45,53,67,78,
可以发现,数组中的个数没有少,还是10个元素,也按升序排列了,但是前面两个数字重复了,其中应该在第2个位置的12变成了3。
说明在排序完成后,又执行了将最小值赋值给数组第二位的操作。于是在代码中找到错误:
if(y==0 && arr[y]>inserted)这里将数组第一位(边界)情况拿出来单独考虑了,已经使arr[0]和arr[1]排序正确,接着下面又if(arr[y]>inserted),当y=0时,arr[0]>inserted肯定为false,于是将inserted中的最小值又赋给了arr[1]。所以出现了上述错误情况。
第二遍:修复bug
package sort;
//直接插入排序
public class StraightInsertionSort {
public static void main(String[] args) {
int[] arr = {12,32,3,45,78,43,53,24,67,45};
int inserted; //临时变量
/*正确,逻辑不是最优
for(int i=1;i<arr.length;i++){ //外层循环,从第二个元素arr[1]开始,到最后一个元素
inserted = arr[i]; //将第二个元素arr[1]的值赋给临时变量
for(int j=i-1;j>=0;j--){
if(j==0 && arr[j]>inserted){//处理arr[0]时的边界情况
arr[j+1] = arr[j]; //首位元素后移
arr[j] = inserted; //最小值放在首位
}else if(arr[j]>inserted){ //(原错误位置)已排好的元素比未排序的元素大时,向后移一格
arr[j+1] = arr[j];
}else{ //已排好的元素不比未排序的元素大时,将未排序元素插入到该元素后一格
arr[j+1] = inserted;
break;
}
}
}
*/
for(int i=0;i<arr.length;i++){
System.out.print(arr[i]+",");
}
}
}
执行结果:3,12,24,32,43,45,45,53,67,78,
其实第一遍的bug就是将边界情况(边界情况指判断比较到数组第一位时的情况,即arr[0]时)执行了两次,所以只要将原来的if(arr[y]>inserted)改成else if(arr[y]>inserted)就行了,这样就不会在比较到arr[0]时执行两次交换赋值的操作。
第三遍:
修改了上面的bug后,我又看了一遍插入排序的原理,发现前面写的代码其实根本没有实现插入排序的意义。因为我将待排序的值分别与前面已经排序好的所有值都进行了比较,这样就变成了另类的冒泡排序了。。。
插入排序在排序过程中,待排序元素的前面是已经从小到大排好序的元素,所以当待排序元素比它的前一个元素大时,就可以结束本次循环,进入下一次循环了,没有必要再分别和更前面的数比较。例:
假设排序进行到第4个元素33(33为待排序元素),33和24比较,33大,由于前面的元素是按从小到大排序好的,所以33已经不需要再和12以及3比较了,这时已经可以进行下一次循环了,即26为待排序元素与它前面的元素进行比较。
3,12,24,|33|,26,45,18,56
所以,我们可以把inserted>arr[j](待排序元素比它的前一个元素大)放在for循环的条件中。代码如下:
package sort;
//直接插入排序
public class StraightInsertionSort {
public static void main(String[] args) {
int[] arr = {12,32,3,45,78,43,53,24,67,45};
int inserted; //临时变量
//优化
for(int i=1;i<arr.length;i++){
inserted = arr[i];
int j = i-1;
for( ; j>=0 && arr[j]>inserted;j--){//优化方法,
arr[j+1] = arr[j]; //充分考虑循环继续下去的条件可知,
} //当已排好的元素不比未排序的元素大时,
arr[j+1] = inserted; //循环也将结束,所以直接放在循环条件中
} //比放在循环内部进行比较要简单
//适用前提是要插入的数前面的数已经排好序了
for(int i=0;i<arr.length;i++){
System.out.print(arr[i]+",");
}
}
}
优化后的代码不仅代码量少了,而且逻辑也简单了,因为这样就不用再单独判断边界情况了。
总结:
这段时间通过敲一些算法的代码练手发现了自己的逻辑有一个很大的问题,就是经常喜欢将一些特殊情况(比如上面一直说的边界情况)单独拿出写一个判断条件,然后进行单独处理。这样也没有错,但是这样做却经常使代码变得复杂,增加了很多不必要的代码量。很多时候,特殊情况其实也可以放在一般情况中处理,亦或者当有多个特殊情况的处理方法相同时,可以将这些情况放在一起处理。
例如,用递归写删除多级目录的demo时(文件和空目录可以直接用delete()方法删除,但是含有子项的目录需要先清空目录),我就将文件、空目录、含有子项的目录全部单独判断,单独处理。但是后来发现文件和空目录都是直接删除,其实可以一起处理,这样就简化了逻辑,也减少了代码量。
用递归写删除多级目录demo的链接:https://blog.csdn.net/qq_39516106/article/details/81263009