排序是接触算法导论的时候最先接触的算法,也是学生时代打交道最多的算法。这几天把几种不同的算法好好回忆一下,包括自己最不熟悉的外排序。
插入排序算法,包括直接插入排序,二分插入排序和希尔排序。
直接上代码
main函数:
public static void main(String[] args) { // TODO Auto-generated method stub int[] is = new int[MAX]; int[] shell = new int[MAX / 2]; for (int i = 0; i < shell.length; i++) { if (i == 0) { shell[i] = MAX / 2; } else { shell[i] = shell[i - 1] / 2; } } shell[shell.length - 1] = 1; for (int i = 0; i < MAX; i++) { // is[i] = new Random().nextInt(MAX); // is[i] = i; is[i] = MAX - i; } long start = System.currentTimeMillis(); shellSort(is, shell); // directSort(is); // binarySort(is); long end = System.currentTimeMillis(); System.out.println("程序执行了" + (end - start) + "毫秒"); }
直接插入排序:
/** * @Title: directSort * @Description: TODO(直接排序) * @param 设定文件 * @return void 返回类型 */ public static void directSort(int[] is) { /** * 第0个是已经排好序的,所以从第一个开始插入 */ for (int i = 1; i < is.length; i++) { /** * 每一个元素与前边已经排好的元素做比较 */ int k; int temp = is[i]; for (k = i - 1; k >= 0 && is[k] > temp; k--) { is[k + 1] = is[k]; } is[k + 1] = temp; } }
直接插入排序的思想最容易理解,就是设想第i个元素前边是已经排序好的数列,像插扑克牌一样,从后往前对比,把第i个元素插到合适的位置。
对100万个随机数进行排序,直插法耗时1分56秒
程序执行了116072毫秒
对100万个已经排序的数组进行排序,直插法耗时5毫秒。。。
对100万个逆序的数组进行排序,直插法耗时6分钟。。。
程序执行了249916毫秒
二分插入排序:
/** * @Title: binarySort * @Description: TODO(二分插入排序) * @param @param is 设定文件 * @return void 返回类型 */ private static void binarySort(int[] is) { /** * 第0个是已经排好序的,所以从第一个开始插入 */ for (int i = 1; i < is.length; i++) { /** * 每一个元素与前边已经排好的元素做比较 */ int temp = is[i]; int left = 0; int right = i - 1; int mid = (left + right) / 2; while (left <= right) { if (temp > is[mid]) { left = mid + 1; } else { right = mid - 1; } mid = (left + right) / 2; } reverse(is, i, left); } }
这种方法也是设想第i个元素前边是已经排序好的数列,不过在插入的时候不是用插纸牌的方法,而是用二分查找的手段,从中间查第i个纸牌应该插入的位置,然后插进去。这种方法比直接插入优越的地方在于如果是乱序的,那么查找的时候时间复杂度只有O(logn)。
二分插入法排序1000000万个随机数耗时1分48秒:
程序执行了108423毫秒
二分插入法排序1000000万个已排序的数组耗时42秒:
二分插入法排序1000000万个倒序的数组耗时3分42秒:
程序执行了224106毫秒
希尔排序:
/** * @Title: shellSort * @Description: TODO(希尔排序) * @param @param is * @return void 返回类型 */ private static void shellSort(int[] is, int[] shell) { for (int i = 0; i < shell.length; i++) { int d = shell[i]; for (int j = 0; j < d; j++) { for (int k = j + d; k < is.length; k = k + d) { int temp = is[k]; int m; for (m = k - d; m >= 0 && is[m] > temp; m = m - d) { is[m + d] = is[m]; } is[m + d] = temp; } } } }
希尔排序类似于直接插入排序。他的思想主要是利用了如果在顺序的情况下,直接排序是几乎不耗时的。首先将原数组分成几个不同的数组分别排序,然后合并再次重新细分,直到最后对整个数组进行直接插入排序。但这时候数组基本有序了,所以时间消耗会小很多。
比如数组{1,2,3,4},首先,希尔排序将数组分成两个子数组,{1,3}和{2,4},对两个子数组分别进行直接插入排序后合并然后再次直接插入排序。希尔排序的思想很像分治算法: 把一个大问题分成若干个小问题,利用小问题的结果来对大问题进行计算,从而使大问题得到优化。
希尔排序1000000万个随机数耗时不到1秒。。。。:
程序执行了209毫秒
希尔排序1000000万个已排序的数组耗时66毫秒:
希尔排序1000000万个倒序的数组耗时82毫秒。
由以上对比可以发现,对于插入排序来说,在数组几乎排好序的情况下,直接插入排序最好,但现实中这种情况不多,现实中大多是乱序情况,这种情况下希尔排序秒杀其他两种插入排序方法。