直接插入排序
核心思想:
- 将数组分为未排序区间和已排序区间
- 每次从未排序区间获取第一个元素,与已排序区间元素从右往左的比较,直到找到正确的索引位置,将元素插入此位置
具体实现方法有两种
整体后移
假定未排序区间的第一个元素为num,则已排序区间内大于num的元素都往后移动一位,直到遇到一个索引位置,这个位置的前一个元素小于num,则当前位置就是num的正确位置,插入元素num的值
package dataStructure;
public class directSort {
public static void main(String[] args) {
int[] a={9,25,12,2};
for (int i = 1; i < a.length; i++) {
int num = a[i];
int j;
for (j = i; j > 0 && num < a[j - 1]; j--) {
a[j] = a[j - 1];
}
a[j] = num;
}
for (int i : a) {
System.out.print(i+" ");
}
System.out.println();
}
i为未排序区间的第一个元素的索引位置,num为未排序区间的第一个元素的值
i 的作用是每次从未排序区间选择第一个元素,然后将这个元素加入到已排序区间,是利用 j 来进行比较和排序的
j-1为已排序区间的每一个元素(因为一开始 j=i,所以 j-1 就来到了已排序区间)
外层for循环用来获取每次未排序区间的第一个元素
内层for循环用来拿num和已排序区间的元素从右到左的进行比较,如果num小于已排序区间的元素
则将已排序区间的元素像右移动,所以是a[j] = a[j - 1];这样直接赋值,而不是交换
这样一直到num大于某个已排序区间的元素,此时停下来,内层for循环终止,此时 j 的索引位置就是num的正确位置
然后是a[j] = num;将一开始的num值放在找到的a[j]位置上
分析一下内层for循环的比较条件
内层for循环的比较条件是j > 0 && num < a[j - 1],因为 j=1 的时候就已经与 [j-1] 也就是位置0的元素比较过了。如果j继续减一,就会出现数组越界,[j-1]的值为-1了
num < a[j - 1],从右往左的与已排序区间的元素进行比较,如果一开始num大于已排序区间的最后一个元素。说明就大于已排序区间的所有元素,不用再去比较,终止内层for循环
两两交换
每次从未排序区间找到第一个元素,然后将元素加入到已排序区间里面。此时已排序区间由于多了一个元素不再有序,因此用两两比较的方式重新进行排序
package dataStructure;
public class directSort {
public static void main(String[] args) {
int[] b={3,6,2,4,1};
for (int i = 0; i < b.length - 1; i++) {
for (int j = i + 1; j > 0; j--) {
if (b[j] < b[j - 1]) {
int temp = b[j];
b[j] = b[j - 1];
b[j - 1] = temp;
}
}
}
for (int i : b) {
System.out.print(i+" ");
}
}
}
上面那种方式是将大于num的数字集体后移,直到num遇到一个小于num的数,停留的位置就是正确的索引位置。将num的值放在这个索引位置上,即可完成这次排序
而这个方法是两两比较,类似于冒泡排序,但不是冒泡排序
外层for循环还是用来指定要比较几次,如果数组一共有10个数,那么只需要比较9次即可
内层for循环,j=i+1用来找到未排序区间的第一个元素(假定还叫num),然后从这个元素开始向左,两两比较。
希尔排序
package dataStructure;
public class shellSort {
public static void main(String[] args) {
int[] arr = {32, 9, 3, 42, 26, 7, 55, 2};
for (int step = arr.length / 2; step > 0; step /= 2) {
//对一个步长区间进行比较 [step,arr.length)
for (int i = step; i < arr.length; i++) {
int value = arr[i];
int j;
for (j = i - step; j >= 0 && arr[j] > value; j -= step) {
arr[j + step] = arr[j];
}
arr[j + step] = value;
}
}
for (int i : arr) {
System.out.print(i+" ");
}
}
}
又到了郁闷的画图时间,画的略丑
总之第一重循环,用来控制step,初开始step的值为arr.length/2 ,即为4
那么一组里面只有两个值,比如第一组就是索引为0和4的元素
第二重循环,用来控制 i,选择是第几组的元素进行比较排序
一开始 i 为4,而 j 为 i-step 即为0
第三重循环,用来控制 j,作用是完成组内元素的比较排序
当组内元素只有两个时,循环的作用就是两两交换,较大的值放在后面。
比较完成后,i ++ ,变成5,然后 j=i-step 变成1,也就是比较下一组两两元素。就这样一直比较下去,直到不满足 i 循环的条件。然后开始第一重 step 循环的第二轮 ,此时step折半变成 2
一组里面不止一个元素了,根据step为2进行分组,一组里面有四个元素
i 循环开始第一轮,此时 i 为2,然后 j 为0,比较排序后,第一组并没有比较完毕,因为一共有四个元素,这里只是比较了两个元素。但是 i++后开始比较下一组,[7,2,9,42] 这一组,直到再次 i ++,i 为 4的时候,才再次回来比较第一组的元素。
这里需要注意的是,此时的第三重循环,j 的循环,里面有三个元素,变成了直接插入排序
i = 2 的时候,比较了两个元素,这两个元素构成了已排序区间,当 i = 4的时候,指向的元素是 32,这个32 就相当于未排序区间的第一个元素,利用第三重 j 循环,进行直接插入排序,采取的手段是整体后移。直到这三个元素比较完毕后,第三重 j 循环结束,i ++ 继续比较[2,7,9,42]这一组,注意这里是2,7,第二组的前两个元素被排序过了
为了便于理解,结合代码详细说一下
for (j = i - step; j >= 0 && arr[j] > value; j -= step) {
arr[j + step] = arr[j];
}
arr[j + step] = value;
这里的 j = i - step 就是指向已排序区间的最后一个元素,而 j + step 就是指向未排序区间的第一个元素。
arr[j + step] = arr[j]; 采取的就是整体后移的方式,将前面较大的元素赋值给后面
比如 i=4时,j = i - step 为2,value = arr[4],如果 arr[2] > value,则arr[4] = arr[2]
然后 j = j - step = 0,如果arr[0] > value,则 arr[2] = arr[0]
最后arr[j + step] = value; arr[0] = value,这里arr[j+step] 为 arr[0] 是因为上一轮循环后 j = j - step 为 -2 ,所以要加 step 变成 0