1、经常使用的排序算法有以下几种:html
一、冒泡排序java
二、选择排序git
三、插入排序算法
四、快速排序数组
五、堆排序数据结构
六、归并排序dom
七、计数排序测试
八、基数排序动画
接下来从原理、代码实现和测试三步对它们进行分析。ui
2、算法分析、实现、测试
一、基类BaseSort.java
该类包含各个排序类的共用部分和生成测试数据功能,其余全部排序算法类都继承该类。具体代码以下:
package test.algorithm; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.util.Random; public class BaseSort { public static int length = 500000; // 测试数据量 public static int[] data = new int[length]; // 测试数据集 public static int lengthOfDigit = 8; // 测试数据的最大位数 public static int maxValue = (int) Math.pow(10, lengthOfDigit); // 测试数据的最大值 /** * 输出数据到控制台 * * @param data */ protected static void printArr(int[] data) { for (int i = 0; i < data.length; i++) { System.out.print(data[i] + " "); } System.out.println(); } /** * 输出数据到文件 * * @param data * @param path */ protected static void printToFile(int[] data, String path) { try { File f = new File(path); if (!f.exists() && !f.createNewFile()) { System.out.println("文件建立失败!"); return; } BufferedWriter output = new BufferedWriter(new FileWriter(f)); for (int i = 0; i < data.length; i++) { output.write(data[i] + "\r\n"); } output.close(); } catch (Exception e) { e.printStackTrace(); } } /** * 交换两个数据 * * @param data * @param i * @param j */ protected static void swap(int[] data, int i, int j) { data[i] = data[i] + data[j]; data[j] = data[i] - data[j]; data[i] = data[i] - data[j]; } /** * 生成测试数据集 */ protected static void genData() { Random r = new Random(); for (int i = 0; i < length; i++) { data[i] = r.nextInt(maxValue); } } }
二、冒泡排序
(1)算法原理:冒泡排序简单的说就是重复比较相邻的两个元素,若是顺序错误就交换使之顺序正确。时间复杂度Ο(n2) ,空间复杂度为Ο(1)。
(2)动画演示
(3)代码实现
package test.algorithm; public class BubbleSort extends BaseSort { public static void bubblesort(int[] data, int start, int end) throws Exception { if (data == null || start < 0 || end < 0) { throw new Exception("参数错误"); } for (int i = start; i < end; i++) { for (int j = end; j > i; j--) { if (data[j] < data[j - 1]) { swap(data, j, j - 1); } } } } public static void main(String[] args) throws Exception { genData(); long startTime = System.currentTimeMillis(); bubblesort(data, 0, length - 1); long endTime = System.currentTimeMillis(); System.out.println("冒泡排序,测试" + length + "个随机数据,耗时:" + (endTime - startTime) + "毫秒"); } }
(4)测试结果
50万数据,时间太长。
3、选择排序
(1)算法原理:选择排序每次从未排序的数据中选择最小的,而后将该元素放到已经排好序的数据末尾,循环直到结束。时间复杂度Ο(n2) ,空间复杂度为Ο(1)。
(2)动画演示
(3)代码实现
package test.algorithm; public class SelectSort extends BaseSort { public static void selectsort(int[] data, int start, int end) throws Exception { if (data == null || start < 0 || end < 0) { throw new Exception("参数错误"); } for (int i = start; i < end; i++) { int minIndex = i; for (int j = i + 1; j <= end; j++) { if (data[j] < data[minIndex]) { minIndex = j; } } if (i != minIndex) { swap(data, i, minIndex); } } } public static void main(String[] args) throws Exception { genData(); long startTime = System.currentTimeMillis(); selectsort(data, 0, length - 1); long endTime = System.currentTimeMillis(); System.out.println("选择排序,测试" + length + "个随机数据,耗时:" + (endTime - startTime) + "毫秒"); } }
(4)测试结果
(最大8位)
四、插入排序
(1)算法原理:插入排序每次从未排序的数据中选取第一个,而后在已经排好序的数据中找到该元素的正确位置,将该元素放到该正确位置,循环直到结束。时间复杂度Ο(n2) ,空间复杂度为Ο(1)。
(2)动画演示
(3)代码实现
package test.algorithm; public class InsertSort extends BaseSort { public static void insertsort(int[] data, int start, int end) throws Exception { if (data == null || start < 0 || end < 0) { throw new Exception("参数错误"); } for (int i = start; i < end; i++) { int curr = data[i + 1]; int j = i + 1; while (j > start && data[j - 1] > curr) { data[j] = data[j - 1]; j--; } if (j != i + 1) { data[j] = curr; } } } public static void main(String[] args) throws Exception { genData(); long startTime = System.currentTimeMillis(); insertsort(data, 0, length - 1); long endTime = System.currentTimeMillis(); System.out.println("插入排序,测试" + length + "个随机数据,耗时:" + (endTime - startTime) + "毫秒"); } }
(4)测试结果
(最大8位)
五、快速排序
(1)算法原理:快速排序每次使用一个元素做为基准,将比基准小的放到前面,比基准大的放到后面,再分别对先后两部分使用快速排序。基准的选择能够任意(下面的代码使用第一个元素)。平均状况下时间复杂度为Ο(n log n), 最坏状况下时间复杂度为Ο(n2) ;空间复杂度为Ο(n)。
(2)动画演示
(3)代码实现
package test.algorithm; public class QuickSort extends BaseSort { public static int partition(int[] data, int start, int end) throws Exception { int lastOne = data[start]; // 使用第一个元素做为基准 int i = start, j = end; while (i < j) { while (i < j && data[j] >= lastOne) { j--; } data[i] = data[j]; while (i < j && data[i] <= lastOne) { i++; } data[j] = data[i]; } data[i] = lastOne; return i; } public static void quicksort(int[] data, int start, int end) throws Exception { if (data == null || data.length <= 0 || data.length < end || data.length < start) { throw new Exception("数据为空或者索引超出范围"); } int index = partition(data, start, end); if (index > start) { quicksort(data, start, index - 1); } if (index < end) { quicksort(data, index + 1, end); } } public static void main(String[] args) throws Exception { genData(); long startTime = System.currentTimeMillis(); quicksort(data, 0, length - 1); long endTime = System.currentTimeMillis(); System.out.println("快速排序,测试" + length + "个随机数据,耗时:" + (endTime - startTime) + "毫秒"); } }
(4)测试结果
(最大8位)
六、堆排序
(1)算法原理:堆排序使用堆这种数据结构进行排序。堆是一个彻底二叉树,知足:大根堆的任意节点的左右子节点(若是有)的值都比自己节点值小,小根堆反之。首先将全部数据生成堆,而后每次将第一个元素和未排好序的数据的最后一个交换,再将除最后一个元素外的数据调整成一个堆。时间复杂度为Ο(n log n), 且空间复杂度为Ο(1)。
(2)动画演示
(3)代码实现
package test.algorithm; public class HeapSort extends BaseSort { private static void adjust(int[] data, int start, int end) { int remain = data[start]; int i = start * 2; int j = start; while (i <= end) { if (i + 1 <= end && data[i] < data[i + 1]) { i++; } if (data[i] < remain) { break; } data[j] = data[i]; j = i; i *= 2; } data[j] = remain; } // 使用大根堆排序 public static void heapsort(int[] data, int start, int end) throws Exception { if (data == null || data.length <= 0 || data.length < end || data.length < start) { throw new Exception("数据为空或者索引超出范围"); } for (int i = end / 2; i >= start; i--) { adjust(data, i, end); } for (int i = end; i > start; i--) { swap(data, start, i); adjust(data, start, i - 1); } } public static void main(String[] args) throws Exception { genData(); long startTime = System.currentTimeMillis(); heapsort(data, 1, length - 1); long endTime = System.currentTimeMillis(); System.out.println("堆排序,测试" + length + "个随机数据,耗时:" + (endTime - startTime) + "毫秒"); } }
(4)测试结果
(最大8位)
七、归并排序
(1)算法原理:归并排序是分治法的一个典型应用。将待排序数据分红两部分,先将这两部分排好序,而后将两部分组合成一个有序的总体。时间复杂度为Ο(n log n),空间复杂度为Ο(n)。
(2)动画演示
(3)代码实现
package test.algorithm; public class MergeSort extends BaseSort { public static void mergesort(int[] data, int start, int end) throws Exception { if (data == null || start < 0 || end < 0) { throw new Exception("参数错误"); } if (start < end) { int mid = (start + end) / 2; mergesort(data, start, mid); mergesort(data, mid + 1, end); int[] tmp = new int[end - start + 1]; int i = start, j = mid + 1, k = 0; while (i <= mid && j <= end) { if (data[i] < data[j]) { tmp[k++] = data[i++]; } else { tmp[k++] = data[j++]; } } while (i <= mid) { tmp[k++] = data[i++]; } while (j <= end) { tmp[k++] = data[j++]; } i = start; j = 0; while (i <= end) { data[i++] = tmp[j++]; } } } public static void main(String[] args) throws Exception { genData(); long startTime = System.currentTimeMillis(); mergesort(data, 0, length - 1); long endTime = System.currentTimeMillis(); System.out.println("归并排序,测试" + length + "个随机数据,耗时:" + (endTime - startTime) + "毫秒"); } }
(4)测试结果
(最大8位)
八、计数排序
(1)算法原理:计数排序是惟一一个不经过比较的排序算法。时间复杂度为Ο(n),空间复杂度为Ο(m)。(m为排序的数据范围)
主要思想是待排序的数据大小必须在必定范围内(最大值为m),开一个m的数组,记录每一个元素出现的次数。基于每一个元素出现的次数,而后将每一个元素以前的元素出现的次数累加(包括自己的值)做为新值,获得的是不比本身小的元素个数,该数值就是该元素应该在的位置。具体下方的动画演示连接。
(2)动画演示:请参考http://www.cs.usfca.edu/~galles/visualization/flash.html
(3)代码实现
package test.algorithm; public class CountSort extends BaseSort { public static void countsort(int[] data, int start, int end) throws Exception { if (data == null || start < 0 || end < 0) { throw new Exception("参数错误"); } int[] countArray = new int[maxValue]; // 清零 for (int i = 0; i < maxValue; i++) { countArray[i] = 0; } // 计数 for (int i = start; i <= end; i++) { countArray[data[i]]++; } // 累加 for (int i = 1; i < maxValue; i++) { countArray[i] += countArray[i - 1]; } int[] sortedArray = new int[length]; // 排序 for (int i = start; i <= end; i++) { countArray[data[i]]--; sortedArray[countArray[data[i]]] = data[i]; } // 复制 for (int i = start; i <= end; i++) { data[i] = sortedArray[i]; } } public static void main(String[] args) throws Exception { genData(); long startTime = System.currentTimeMillis(); countsort(data, 0, length - 1); long endTime = System.currentTimeMillis(); System.out.println("计数排序,测试" + length + "个随机数据,耗时:" + (endTime - startTime) + "毫秒"); } }
(4)测试结果
(最大8位)
九、基数排序
(1)算法原理:基数排序是从右到左一位一位的比较排序的算法。待排序元素须要有已知最大位数,先按照个位排序(个位相同的以出现顺序为准),而后按照十位进行排序......时间复杂度为Ο(nm),空间复杂度为Ο(n)。(n为元素个数,m为元素的位数)
(2)动画演示:请参考http://www.cs.usfca.edu/~galles/visualization/flash.html
(3)代码实现
package test.algorithm; public class RadixSort extends BaseSort { private static int digitOfIndex(int data, int index) throws Exception { if (index < 1) { throw new Exception("位数不能小于1"); } data = data / ((int) Math.pow(10, index - 1)); int digit = data % 10; return digit; } public static void radixsort(int[] data, int start, int end, int lengthOfDigit) throws Exception { if (data == null || start < 0 || end < 0) { throw new Exception("参数错误"); } for (int i = 1; i <= lengthOfDigit; i++) { int[][] list = new int[10][length]; // list[x][0]保留该组数据个数 for (int j = start; j <= end; j++) { int digit = digitOfIndex(data[j], i); list[digit][++list[digit][0]] = data[j]; } int k = start; for (int j = 0; j <= 9; j++) { for (int l = 1; l <= list[j][0]; l++) { data[k++] = list[j][l]; } } } } public static void main(String[] args) throws Exception { genData(); long startTime = System.currentTimeMillis(); radixsort(data, 0, length - 1, lengthOfDigit); long endTime = System.currentTimeMillis(); System.out.println("基数排序,测试" + length + "个随机数据,耗时:" + (endTime - startTime) + "毫秒"); } }
(4)测试结果
(最大8位)
3、各算法对比
以下表格对上述各算法进行了简要对比,表格中n为元素数量。
时间复杂度 | 空间复杂度 | 测试500 | 测试5千 | 测试5万 | 测试50万,6位 | 测试50万,8位 | 备注 | |
冒泡排序 | Ο(n2) | Ο(1) | 5ms | 32ms | 3722ms | 时间太长 | 时间太长 | |
选择排序 | Ο(n2) | Ο(1) | 2ms | 14ms | 932ms | 92339ms | 92540ms | |
插入排序 | Ο(n2) | Ο(1) | 1ms | 11ms | 614ms | 60390ms | 59402ms | |
快速排序 | Ο(n log n) | Ο(n) | 0ms | 4ms | 12ms | 58ms | 59ms | |
堆排序 | Ο(n log n) | Ο(1) | 0ms | 4ms | 7ms | 59ms | 59ms | |
归并排序 | Ο(n log n) | Ο(n) | 0ms | 6ms | 31ms | 98ms | 98ms | |
计数排序 | Ο(n) | Ο(m) | 8ms | 8ms | 9ms | 25ms | 256ms | m为排序的数据范围 |
基数排序 | Ο(nm) | Ο(n) | 1ms | 4ms | 21ms | 53ms | 65ms | m为元素的最大位数 |
从对比结果能够看出:
一、最简单的冒泡排序毫无疑问效率是最差的,并且与其余算法差异很明显;
二、选择排序和插入排序算法实现难度差很少,效率也相差不大;
三、快速排序、堆排序、归并排序、计数排序、基数排序从实际测试来看,时间消耗上差异都在同一个数量级;
四、除了计数排序是基于数据的位数,对数据位数(如8位数比6位数就相差一个数量级)比较敏感,其余排序算法基本上没有影响。