希尔排序也是一种插入排序,只不过是他的步长更大
插入排序属于内部排序,他的基本思想是把n个待排序的元素看成一个有序表、一个无序表,从无序表中挑选出元素,依次找到合适的位置插入有序表,直到无序表中的元素都插入到了有序表,n个元素变成了有序的。
从上面的动图我们可以看出,在比较的过程中有一个元素移动的过程,这个移动的步骤是我们代码的最内部动作。有序、无序数组的长度变化过程是通过一个for循环实现的,在寻找插入位置的过程是由一个for循环控制的,这两个for循环是嵌套的,内层的循环长度收外层for的影响,在两个for循环之内是有一个if的判断体,这个判断是用来判断是否找到了合适的元素,但是无论有没有找到合适的元素,我们元素的移动都是需要的。这是我们单纯的通过for实现的一个思路但是对于我们这种直到一个合适条件的判断题,是不是使用while的循环更加直接呢?使用while的时候我们通过while来实现对需要后移的元素进行后移,直到找到了一个合适的插入位置来跳出,首先需要后移的元素是要满足,前面的元素大于待插入的元素,并且索引不超出边界(insertIndex>=0&&insertVal<arr[insertIndex],这个循环条件的设置很有技巧,如果两个颠倒位置看着貌似可以提高算法的效率,但是他会忽略一种情况,就是头部元素的丢失,不能够正常的往后移动,在下面的图中没有改过来,看的时候要注意一下)跳出了循环之后是不是找到了待插入的位置呢?如果待插入的元素本来就不需要进行移动呢?我们通过一个if(insertIndex+1!=i)来进行判断,判断条件产生是在合理位置的前一个元素,所以我们还应该对insertIndex+1,在进行赋值。
希尔排序就是把记录下标的一定增量分组,对魅族使用直接插入排序算法,随着增量的逐渐减少,每组的关键字越来越多,当增量减到1,这个文件便被分成了一组,我们实现一次插入排序,实现对元素的排序。希尔排序就是通过前面的大步长将需要很多步的元素尽快的移动到合适的位置,避免这样的情况出现:
希尔排序的示意图:
在初学的时候有很多人觉得希尔排序更像是冒泡排序的升级版,如果要是有这种想法的话,一定要再好好看看代码的实现,他确实是插入排序的一种实现思路而非冒泡!
两种排序算法的实现都可以有多种,for、while、递归,又有那种是更优雅的呢?
插入排序算法:
这里通过while循环的实现感觉就是很巧妙,直接通过while循环实现所有应该后移的情况,再通过一个if来判断需要插入的情况。
希尔排序算法:
在这里离while与for的实现个人感觉没有太大的差别,但是while的实现好像更加优雅一些,针对算法代码的进一步优化,我们可以进一步的进行。在实现的过程中个人理解的最大障碍是第二层的循环。当初的思维是局限于要先把一个步长组中的元素排序完成,但是事实是他们可以交替进行且不会干扰,因为他们的中间已经有步长隔开了。
在未排序的元素中挑选元素插入有序队列,不同的步长不同的实现方式,他们的运行性能又是怎么样的呢?
针对算法进行测试的一个代码样板,我们要做的就是把@FunctionName处改换为我们自己的名字:
static final int arrSize = 80000;
static long longStartTime = 0;
static long longEndTime = 0;
public static void main(String[] args) {
System.out.println("测试数据开始生成-------------->");
int arrSmall[] = {3, 9, -1, 10, -2};
int arrBig[] = new int[arrSize];
for (int i = 0; i < arrSize; i++) {
arrBig[i] = (int) (Math.random() * arrSize); //生成一个[0, 8000000) 数
}
System.out.println("小数据量测试开始-------------->");
@FunctionName(arrSmall);
System.out.println("小数据量顺序测试:" + Arrays.toString(arrSmall));
System.out.println();
longStartTime = Calendar.getInstance().getTimeInMillis();
System.out.println("大数量测试开始运行===========>");
//测试冒泡排序
@FunctionName(arrBig);
longEndTime = Calendar.getInstance().getTimeInMillis();
System.out.println(arrSize + "大数据量性能测试,花费的时间是:" + (longEndTime - longStartTime) / 1000 + "秒");
}
插入排序上述比较的两种代码实现:
//通过递归的方式实现,元素是从后面开始的
public static void insertSortByWhile(int[] arr){
//这里实现的思路是从后往前拍的,所以就可以直接通过while进行后移,相比for的操作
//可以看成是省去了比较的步骤
for (int i = 1; i < arr.length; i++) {
int insertVal = arr[i];
int insertIndex = i-1;
//这个判断条件的设置很有技巧,
while(insertIndex>=0&&insertVal<arr[insertIndex]){
arr[insertIndex+1] = arr[insertIndex];
insertIndex --;
}
//我的待插入的数据从后面过来的,所以如果比较不成功,跳出while说明待插入的值
//应该要插入的位置是在于他比较的值得后面,所以这里应该加1
if(insertIndex+1!=i){
arr[insertIndex+1] = insertVal;
}
}
}
public static void insertSortByDoubleFor(int[] arr) {
for (int i = 1; i < arr.length; i++) {
// System.out.println("**********"+i);
int temp = 0;
int insertVal = arr[i];
for (int j = i - 1; j >= 0; j--) {
if (arr[j] <= insertVal && j == i - 1) {
// System.out.println(insertVal+"前面是:"+arr[j]+"不用插入的数据");
break;
} else {
// System.out.println(insertVal+"前面是:"+arr[j]+"需要插入的数据");
if (arr[j] < insertVal) {
// System.out.println("已经找到了待插入的位置:"+arr[j+1]+"要插入的是:"+insertVal);
arr[j + 1] = insertVal;
break;
} else if (arr[j] >= insertVal && j != 0) {
// System.out.println(arr[j]+"大于待比较元素 "+insertVal+" 发生了后移");
arr[j + 1] = arr[j];
} else if (arr[j] >= insertVal && j == 0) {
arr[j + 1] = arr[j];
arr[j] = insertVal;
}
}
}
}
}
上面的两种代码实现都是从后面开始比的,如果我们冲前面开始比较,找到合适的位置之后再对需要移位的元素进行一个移位呢?实现代码如下:
//通过for循环的形式实现,元素是从前面开始的
public static void insertSortByFor(int[] arr){
//这里实现的思路是从前往后比较,得到确定的位置之后再进行位移
for (int i = 1; i < arr.length; i++) {
int temp =0;
for (int j = 0; j < i; j++) {
//arr[i]这个数小于了arr[j]的这个数,arr[i]要放在arr[j]
//先把arr[i]这个位置的数记录一下,随后把arr[j]以及之后的一段数据后移一位,腾出arr[j]的位置
if (arr[i]<arr[j]){
temp = arr[i];
for (int k = i;k > j ;k--){
arr[k] = arr[k-1];
}
arr[j] = temp;
}
}
}
}
希尔排序代码的两种实现方式:
public static void shellSortByFor(int[] arr) {
int exchangeTemp;
for (int gap = arr.length; gap > 0; gap /= 2) {
for (int i = gap; i < arr.length; i++) {
for (int j = i - gap; j >= 0; j -= gap) {
if (arr[j] > arr[j + gap]) {
exchangeTemp = arr[j];
arr[j] = arr[j + gap];
arr[j + gap] = exchangeTemp;
break;
}
}
}
}
}
public static void shellSortByWhile(int[] arr) {
for (int gap = arr.length; gap > 0; gap /= 2) {
for (int i = gap; i < arr.length; i++) {
int j = i;
int temp = arr[j];
while (j - gap >= 0 && temp < arr[j - gap]) {
//移动
arr[j] = arr[j - gap];
j -= gap;
}
//当退出while后,就给temp找到插入的位置
arr[j] = temp;
}
}
}