插入排序和希尔排序 Java实现以及实际效率对比

插入排序 以及其改进型 希尔排序的Java实现以及实际性能对比

简单对插入排序和希尔排序做一个介绍,以及给出Java实现,之后简单分析下两者的时间复杂度,以及实际表现

插入排序:

一般从数组第二个元素(下标为1)开始,要求在该元素之前的元素总保持有序。具体的定义参考百度百科

这里用一张图简单表示一下:

 

 

希尔排序:

带有一种步长的排序,步长的最后一步总是1

可以认为普通的插入排序是希尔排序的最后一步,但因为希尔排序在最后一步前已调整好了大部分元素的顺序,因此相对耗时的交换赋值操作比插入排序要少很多。

不光是在最后一步,从总体来看希尔排序比插入排序快就快在交换赋值操作要少。

在稍后的测试中可以看到,在数据量超过10000的时候,插入排序的效率远远不如希尔排序

希尔排序的具体原理及实现参考:

希尔排序

 

插入排序和希尔排序的Java实现

插入排序:

package com.ryo.algorithm.sort;

/**
 * 插入排序
 * @author shiin
 */
public class InsertionSort implements Sort{
	
	@Override
	public int[] sort(int[] arr) {
		int temp ;
		int j;
		for(int i=1 ;i<arr.length ;i++) {
			temp = arr[i];
			j = i-1;
			//这里要写成强且 否则当j=-1时第二个判断将会抛出outofindex异常
			while(j > -1 && arr[j] > temp) {
				arr[j+1] = arr[j];
				j--;
			}
			arr[j+1] = temp;
		}
		return arr;
	}

}

希尔排序:

package com.ryo.algorithm.sort;

/**
 * 这里默认数组的长度将大于37
 * @author shiin
 *
 */
public class ShellSort implements Sort{
	
	//步长序列
	private int[] steplist = {373,181,83,37,17,7,3,1};

	@Override
	public int[] sort(int[] arr) {
		//输入数组长度
		int len = arr.length;
		//进行子排序次数
		int times = 0;
		//当前步长
		int step;
		//临时存储的变量
		int temp;
		//临时存储的索引
		int m;
		for(;times < steplist.length ;times++) {
			step = steplist[times];
			//这里i相当于是一个偏移量 最大偏移不能超过步长
			for(int i=0 ;i<step ;i++) {
				//j,j+step,j+2*step......为被分到一组的下标
				//这里开始和直接插入排序相似,可以认为直接插入排序的step为1
				for(int j=i ;j<len &(j+step)<len;j=j+step) {
					temp = arr[j+step];
					m = j;
					while(m > -1 && arr[m] > temp) {
						arr[m+step] = arr[m];
						m = m-step;
					}
					arr[m+step] = temp;
				}
			}
		}
		return arr;
	}
}

用于测试的类:

packagecom.ryo.algorithm.sort;
 
import com.ryo.algorithm.util.Util;
 
public class DoSort {
    
    private int[] result = new int[20000];
 
    public static void main(String[] args) {
       DoSort ds = new DoSort();
       long before = System.currentTimeMillis();
       
       //在这一行更换需要测试的方法
       ds.testShellSort();
       
       long after = System.currentTimeMillis();
       for(intn : ds.getResult()) {
           System.out.println(n);
       }
       System.out.println("total time : "+(after-before)+"ms");
    }
    
    public DoSort() {
       for(inti=0 ;i<result.length ;i++) {
           //这里是已写好的随机创建0-10000随机数的方法
           result[i] = Util.random();
       }
    }
    
    public void testInsertionSort() {
       new InsertionSort().sort(result);
    }
    
    public void testShellSort() {
       new ShellSort().sort(result);
    }
    
    public int[] getResult() {
       return this.result;
    }
 
}

性能对比:

测试环境:Jdk 1.8.0_161    eclipse Version: Oxygen.3a Release (4.7.3a)

测试数据:数组长度为20000,存有0-20000随机整数, 事先跑数次使得运行时间稳定,再取三次运行的平均值 ,初始化等额外操作均不在计时范围内

运行时间结果:

插入排序

   

平均时间:147ms

希尔排序

  

平均时间:7ms

实际上因为这里取的步长最大才为373,多取几个步长运行时间还会更小,步长的取值直接影响到希尔排序的效率,且影响非常大

这里在private int[] steplist ={373,181,83,37,17,7,3,1};的基础上附加一个7331607,再跑起来发现时间基本可以稳定在6ms

由这里的结果对比可以看到,在数据量超过10000的时候,希尔排序的效率远远大于直接插入排序,若数据量继续增大,插入排序所需要的时间将增长的更快。

 

关于直接插入排序和希尔排序的时间复杂度

直接插入排序的平均时间复杂度为O(n^2),最好情况下(即已排好序)时间复杂度为O(n),最差情况下(即逆序排列)的时间复杂度为O(n^2)

希尔排序的平均时间复杂度在选择较好的步长时应该能接近O(n^1.3),最坏和最优的时间复杂度和直接插入排序相同

由于希尔排序的效率跟程序选择的步长紧密相关,关于希尔排序步长选择的研究其实是一个数学难题

在本程序中对比实际效率:

理论效率倍数:20000^2 / 20000^1.3 = 1025

实际效率倍数:147 / 7 = 21

1025 >> 21

由于1025只是理论效率倍数,但由于时间复杂度的计算中只包括了主要的耗时操作,一些非关键操作直接认为耗时为0,且还有Java环境、内存存取等影响,造成了数值偏差较大

但我们依然可以看到两种算法实际效率之间存在较大的差距

 

 

猜你喜欢

转载自blog.csdn.net/my_dearest_/article/details/79922643
今日推荐