希尔排序
简单的插入排序存在的问题
我们看简单的插入排序可能存在的问题
数组arr={2,3,4,5,6,1},这个时候需要插入的数1(最小),这样的过程是:
{2,3,4,5,6,6}
{2,3,4,5,5,6}
{2,3,4,4,5,6}
{2,3,3,4,5,6}
{2,2,3,4,5,6}
{1,2,3,4,5,6}
,我们明显上面排序是从小到大,明显最后一个如果较小,那么我们移动的次数会很多,对效率有印象,所以对于这样的方式,我们提出来一个方案,希尔排序
希尔排序法介绍
希尔排序(Donald Shell)于1959年提出的一种排序算法,希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序
希尔排序法基本思想
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序,随着增加量逐渐减少,每组包含的关键词越来越多,每当增量减至1时,整个文件恰好被分为一组
希尔排序图解
希尔排序时,我们用两种方法.
- 对有序序列在插入时采用交互法,并测试排序速度
- 对有序序列在插入时采用移动发,并测试排序速度
交换法的希尔排序
逐步推到方法
package sort;
import java.util.Arrays;
public class ShellSort {
public static void main(String[] args) {
int[] arr={8,9,1,7,2,3,5,4,6,0};
shellSort(arr);
}
//使用逐步推到的方式来编写我们的Shell排序
public static void shellSort(int[] arr){
int temp=0;
//希尔排序的第一轮排序
/*思路
* 1,因为第一轮排序,是将十个数据分成了五组,那么这个时候我们就按五组进行处理*/
for (int i=5;i<arr.length;i++){
//遍历各组中所有的元素(共五组,每组有两个元素),步长是5
for (int j=i-5;j>=0;j-=5){
//如果当前的元素,大于加上步长后的那个元素,说明需要交换
if(arr[j]>arr[j+5]){
temp=arr[j];
arr[j]=arr[j+5];
arr[j+5]=temp;
}
}
}
System.out.println("希尔排序一轮后输出:"+ Arrays.toString(arr));
//希尔排序的第二轮排序
/*思路
* 1,因为第二轮排序,我们缩小增量,原来的增量在除以2,此时是2.5,取整数,2*/
for (int i=2;i<arr.length;i++){
//遍历各组中所有的元素(共五组,每组有两个元素),步长是5
for (int j=i-2;j>=0;j-=2){
//如果当前的元素,大于加上步长后的那个元素,说明需要交换
if(arr[j]>arr[j+2]){
temp=arr[j];
arr[j]=arr[j+2];
arr[j+2]=temp;
}
}
}
System.out.println("希尔排序二轮后输出:"+ Arrays.toString(arr));
//希尔排序的第三轮排序
/*思路
* 1,因为第二轮排序,我们缩小增量,原来的增量在除以2,此时是1,*/
for (int i=1;i<arr.length;i++){
//遍历各组中所有的元素(共五组,每组有两个元素),步长是5
for (int j=i-1;j>=0;j-=1){
//如果当前的元素,大于加上步长后的那个元素,说明需要交换
if(arr[j]>arr[j+1]){
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
System.out.println("希尔排序三轮后输出:"+ Arrays.toString(arr));
}
}
通过逐步推到方式,有很多人在第一次看代码的时候,逐步推到都用到了两个for循环,为什么还能解决简单插入排序产生的问题,其实,我们在开始分成产生我们的第一个增量的时候,我们就对大小做了一次,调整,所有我们后来每一次,内循环中的if条件是几乎不怎么进去的,所有我们更本不会做if条件里面的那些交换,就解决了简单插入排序产生的交换次数过多的问题.
Shell Sorting
package sort;
import java.util.Arrays;
public class ShellSort {
public static void main(String[] args) {
int[] arr={8,9,1,7,2,3,5,4,6,0};
shellSort(arr);
}
//使用逐步推到的方式来编写我们的Shell排序
public static void shellSort(int[] arr){
int temp=0;
//根据前面的逐步分析,我们使用循环处理,gap是我们的增量值
for(int gap=arr.length/2;gap>0;gap/=2){
for (int i=gap;i<arr.length;i++){
//遍历各组中所有的元素(共五组,每组有两个元素),步长是5
for (int j=i-gap;j>=0;j-=gap){
//如果当前的元素,大于加上步长后的那个元素,说明需要交换
if(arr[j]>arr[j+gap]){
temp=arr[j];
arr[j]=arr[j+gap];
arr[j+gap]=temp;
}
}
}
System.out.println("希尔排序x轮后输出:"+ Arrays.toString(arr));
}
}}
但是我们的上面的插入法的希尔排序其实比我们简单的插入排序更加耗时,时间复杂度更大,这个是为什么呢?因为我们虽然没有走if里面的,但是我们走了三个for循环,而且,我们发现一个还是进行了一次交换,也是要耗费时间的.我们的简单插入法,我们发现一个比较大的并没有交换,而是移位,然后再到满足我们插入条件的时候才插入.
移动法的希尔排序
package sort;
import java.util.Arrays;
public class ShellSort {
public static void main(String[] args) {
int[] arr = {8, 9, 1, 7, 2, 3, 5, 4, 6, 0};
shellSort2(arr);
//选择排序的速度测试
int[] arr2 = new int[80000];
for (int i = 0; i < 80000; i++) {
arr2[i] = (int) (Math.random() * 40000);//生成一个[0,20000)的随机整数
}
long startTime = System.currentTimeMillis();
shellSort2(arr2);
long endTime = System.currentTimeMillis();
System.out.println("排序用的时间:" + (endTime - startTime));
}
//对交换式的希尔排序进行优化,->移位置法
public static void shellSort2(int[] arr){
//增量gap,并逐步缩小增量
for (int gap=arr.length/2;gap>0;gap/=2){
//从第gap个元素,逐个对其所在的组进行直接插入排序
for (int i=gap;i<arr.length;i++){
int index=i;//待插入的i
int temp=arr[index]; //此时arr[i]的值
if(arr[index]<arr[index-gap]){
while ((index-gap)>=0&&temp<arr[index-gap]){
//移动
arr[index]=arr[index-gap];
index-=gap;//index往前面移动
}
//当退出我们while循环后就给temp找到l插入的位置
arr[index]=temp;
}
}
}
System.out.println(Arrays.toString(arr));
}
}