最近在看一些代码优化相关的东西,下午看到排序这块,常用的排序方式有冒泡排序、选择排序、快速排序等,这里记录下这三种排序的Java实现。最后附有2个测试这几种排序方式的时间的代码
一、几种常用排序方式介绍
1.冒泡排序
以升序排序为例,将序列看成一排竖着的气泡,最后一个元素与倒数第二个元素进行比较,小的往前移,再将倒数第二个元素与倒数第三个元素比较,依次类推,第一轮比较后,最小的就到了位置1,同样第二轮比较后第二小的到了位置二~
#PopSortDemo.java
/** * 冒泡排序 * * @author admin * */ public class PopSortDemo implements Sorted { public void sort(int[] array) { for (int i = 0; i < array.length; i++) { for (int j = array.length - 1; j > i; j--) { if (array[j] < array[j - 1]) { SortUtil.exchange(array, j, j-1); } } } } }
总结:
1). 需要比较的次数和数组长度有关[1+2+3+4~+(n-1)]
2). 需要排序数组长度越长,效率下降明显:n为10时需要比较45次,但n为100时比较次数直线上升到4950次,相差100多倍
2.冒泡排序(改进算法)
针对冒泡算法,如果有一轮没有发生交换,说明每个相邻位置不需要交换,即每个位置已经排好了,后面就可以不用继续了
#PopSortImproveDemo.java
/** * 冒泡排序算法(改进):如果有一轮没有发生交换,说明位置已经排好了,后面就可以不用继续了 * @author admin * */ public class PopSortImproveDemo implements Sorted { public void sort(int[] array) { boolean changed = true; for (int i = 0; i < array.length && changed; i++) { changed = false; for (int j = array.length - 1; j > i; j--) { if (array[j] < array[j - 1]) { changed = true; SortUtil.exchange(array, j, j - 1); } } } } }
总结:
1). 需要比较的次数和数组长度有关,最多比较[1+2+3+4~+(n-1)] 次,最少比较[n-1]次(初始就是有序的)
3.选择排序
先找出最小的数,放到第一个,找出后面数中,第二小的,放到第二个,以此类推。
/** * 选择排序 * * @author admin * */ public class SelectSortDemo implements Sorted { public void sort(int[] array) { int pos; for (int i = 0; i < array.length; i++) { pos=i; for (int j = i + 1; j < array.length; j++) { if (array[pos] > array[j]) { pos=j; } } if(pos!=i) SortUtil.exchange(array, pos, i); } } }
总结:
1). 比较次数和冒泡排序算法相同
2). 之前一直把选择排序记成冒泡排序,实际有区别的(*+﹏+*)~@
4.快速排序
选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素,此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分
/** * 快速排序:选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素,此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分 * @author admin * */ public class QuickSortDemo implements Sorted { public void sort(int[] array) { sort(array, 0, array.length - 1); } /** * 递归调用排序:排序基准位置前后子数组 * * @param array * @param start * @param end */ public void sort(int[] array, int start, int end) { if (start < end) { int pos = findMiddle(array, start, end); sort(array, start, pos - 1); sort(array, pos + 1, end); } } /** * 获取基准元素位置 */ public int findMiddle(int[] array, int start, int end) { int temp = array[start]; while (start < end) { // 结束位置递减,直到找到第一个比标准位置值小的 while (start < end && array[end] >= temp) { end--; } // 基准位置交换到end array[start] = array[end]; // 开始位置递增,直到找到第一个比标准位置值大的 while (start < end && array[start] <= temp) { start++; } // 基准位置交换到start array[end] = array[start]; } // 记录基准位置值 array[start] = temp; return start; } }
总结:
1) 速度比较快,当然前提是基准位置找的好\(`▽′)/
2) 最差的情况,需要找[n-1]次基准位置,找基准位置的过程中总共循环比较[1+2+3+4~+(n-1)]次
二、排序方式时间测试
几种排序的时间复杂度
排序法 |
最差时间分析 | 平均时间复杂度 | 稳定度 | 空间复杂度 |
冒泡排序 | O(n2) | O(n2) | 稳定 | O(1) |
快速排序 | O(n2) | O(n*log2n) | 不稳定 | O(log2n)~O(n) |
选择排序 | O(n2) | O(n2) | 稳定 | O(1) |
二叉树排序 | O(n2) | O(n*log2n) | 不一顶 | O(n) |
插入排序 |
O(n2) | O(n2) | 稳定 | O(1) |
堆排序 | O(n*log2n) | O(n*log2n) | 不稳定 | O(1) |
希尔排序 | O | O | 不稳定 | O(1) |
这里直接测试每种排序方式消耗的时间,采用两种方式:一种是每次排序都重新设置数组每个元素的值,第二种是不同的排序方式排序同一个数组
2. 不同排序方式,不同的数组
>测试代码:
/** * 排序测试,每种排序方式对不同随机数组排序多次,取平均时间 * * @author admin * */ public class RandomSortTest { private static Logger log = LogManager.getLogger(); public static void main(String[] args) { // 87, 76, 65, 51, 43, 31, 23, 17, 6, 3 // 6, 17, 23, 31, 43, 51, 65, 76, 87, 3 // 3, 6, 17, 23, 31, 43, 51, 65, 76, 87 // 数组长度 & 数组元素随机生成的最大值 & 每种排序方式测试次数 int len = 100000, numMax = 10000,sortTypeCount = 10; // 待排序数组 int[] arr = new int[len]; // 排序类 Sorted sorter = null; // 循环调用多个排序方法,每次调用getSorter()会返回不同的排序方法 while ((sorter = getSorter()) != null) { long time = 0; for (int i = 0; i < sortTypeCount; i++) { // 给每个数组元素设置随机值 SortUtil.insertRandomNumber(arr, numMax); // 排序,并返回排序时间 time += SortUtil.testSortTime(arr, sorter); } log.info("排序方法[{}],平均[{}/{}]耗时{}ms", sorter.getClass().getSimpleName(), time, sortTypeCount, time / sortTypeCount); } } /** * 排序方式:1、快速排序;2、选择排序;3、冒泡排序;4、冒泡排序(改进) */ private static int sortType = 1; private static Sorted getSorter() { Sorted sorter = null; // 1、快速排序;2、选择排序;3、冒泡排序;4、冒泡排序(改进) switch (sortType++) { case 1: sorter = new QuickSortDemo(); break; case 2: sorter = new SelectSortDemo(); break; case 3: sorter = new PopSortDemo(); break; case 4: sorter = new PopSortImproveDemo(); break; default: break; } return sorter; } }
>测试结果(len=10W):
2017-03-22 22:30:11.214 [main] INFO - 排序方法[QuickSortDemo],平均[92/10]耗时9ms cn.tinyf.demo.sort.RandomSortTest 2017-03-22 22:30:38.408 [main] INFO - 排序方法[SelectSortDemo],平均[27182/10]耗时2718ms cn.tinyf.demo.sort.RandomSortTest 2017-03-22 22:33:04.408 [main] INFO - 排序方法[PopSortDemo],平均[145987/10]耗时14598ms cn.tinyf.demo.sort.RandomSortTest 2017-03-22 22:35:26.352 [main] INFO - 排序方法[PopSortImproveDemo],平均[141931/10]耗时14193ms cn.tinyf.demo.sort.RandomSortTest>测试结果(len=30):
2017-03-22 23:10:35.963 [main] INFO - 排序方法[QuickSortDemo],耗时33843纳秒 cn.tinyf.demo.sort.SortTest 2017-03-22 23:10:35.964 [main] INFO - 排序方法[SelectSortDemo],耗时85253纳秒 cn.tinyf.demo.sort.SortTest 2017-03-22 23:10:35.964 [main] INFO - 排序方法[PopSortDemo],耗时224286纳秒 cn.tinyf.demo.sort.SortTest 2017-03-22 23:10:35.965 [main] INFO - 排序方法[PopSortImproveDemo],耗时166954纳秒 cn.tinyf.demo.sort.SortTest3. 不同排序方式,相同的数组
>测试代码:
/** * 排序测试,针对相同的数组排序 * * @author admin * */ public class SortTest { private static Logger log = LogManager.getLogger(); public static void main(String[] args) { // 数组长度,数组元素随机生成的最大值 int len = 100000, numMax = 10000; // 源数组 int[] src = new int[len]; // 待排序数组 int[] arr = new int[len]; // 给每个数组元素设置随机值 SortUtil.insertRandomNumber(src, numMax); // 排序类 Sorted sorter = null; // 循环调用多个排序方法,每次调用getSorter()会返回不同的排序方法 while ((sorter = getSorter()) != null) { long time = 0; // 设置待排序数组 System.arraycopy(src, 0, arr, 0, len); // 排序,并返回排序时间 time += SortUtil.testSortTime(arr, sorter); log.info("排序方法[{}],耗时{}ms", sorter.getClass().getSimpleName(), time); } } /** * 排序方式:1、快速排序;2、选择排序;3、冒泡排序;4、冒泡排序(改进) */ private static int sortType = 1; private static Sorted getSorter() { Sorted sorter = null; // 1、快速排序;2、选择排序;3、冒泡排序;4、冒泡排序(改进) switch (sortType++) { case 1: sorter = new QuickSortDemo(); break; case 2: sorter = new SelectSortDemo(); break; case 3: sorter = new PopSortDemo(); break; case 4: sorter = new PopSortImproveDemo(); break; default: break; } return sorter; } }>测试结果(len=10W):
2017-03-22 22:28:38.228 [main] INFO - 排序方法[QuickSortDemo],耗时13ms cn.tinyf.demo.sort.SortTest 2017-03-22 22:28:40.854 [main] INFO - 排序方法[SelectSortDemo],耗时2625ms cn.tinyf.demo.sort.SortTest 2017-03-22 22:28:55.803 [main] INFO - 排序方法[PopSortDemo],耗时14949ms cn.tinyf.demo.sort.SortTest 2017-03-22 22:29:10.574 [main] INFO - 排序方法[PopSortImproveDemo],耗时14770ms cn.tinyf.demo.sort.SortTest3.小结
- 数组元素比较多时快速排序效率比较高
#Sorted接口
public interface Sorted { void sort(int[] array); }
#SortUtil工具类
/** * 排序工具类 * * @author admin * */ public class SortUtil { /** * 交换数组两个位置元素值 * * @param array * @param pos1 * @param pos2 */ public static void exchange(int[] array, int pos1, int pos2) { int temp = array[pos1]; array[pos1] = array[pos2]; array[pos2] = temp; } /** * 随机数生成器 */ private static Random random = new Random(); /** * 深层随机数 * * @param max * 最大值 * @return */ public static int randomInteger(int max) { return random.nextInt(max); } /** * 使用随机数设置数组每个位置的元素值 * * @param arr * @param randomMax */ public static void insertRandomNumber(int[] arr, int randomMax) { for (int i = 0; i < arr.length; i++) { arr[i] = SortUtil.randomInteger(randomMax); } } /** * 测试排序时间 * * @param arr * @param sorter * @return */ public static long testSortTime(int[] arr, Sorted sorter) { long start = System.currentTimeMillis(); sorter.sort(arr); return System.currentTimeMillis() - start; } /** * 数组反序 * * @param array */ public static void reverse(int[] array) { for (int i = array.length / 2 - 1; i >= 0; i--) { exchange(array, i, array.length - 1 - i); } } }