Java 与常用排序算法
Comparison
- 时间复杂度
- 空间复杂度
算法在计算机上所占用的存储空间,包括存储算法本身所占用的存储空间、算法的输入输出数据所占用的存储空间和算法在运行过程中临时占用的存储空间这三个方面。
但在评估一个算法的优良性的时候,前两个基本是固定的,只有算法运行是临时占用的存储空间才会随着数据量的增大而有较大影响。因此,一般以程序运行时占用的临时存储大小作为评估指标。
快速排序 (Divide and Conquer)
1 public void quickSort(int[] arr, int l, int r) { 2 if (l < r) { 3 int p = partition(arr, l, r); // divide and conquer 4 quickSort(arr, l, p - 1); 5 quickSort(arr, p + 1, r); 6 } 7 } 8 9 // select the first element as pivot 10 // use do while loop 11 public int partition(int[] arr, int l, int r) { 12 int i, j, pivot, tmp; 13 pivot = arr[l]; 14 i = l; 15 j = r + 1; 16 17 do { 18 for (i++; i < r && arr[i] <= pivot; i++) ;// do while loop, less than or equal to pivot 19 for (j--; j > l && arr[j] > pivot; j--) ;// do while loop, greater than pivot 20 21 // swap arr i,j 22 tmp = arr[i]; 23 arr[i] = arr[j]; 24 arr[j] = tmp; 25 } while (i < j); 26 if (i > j) { // cross line, swap back 27 tmp = arr[i]; 28 arr[i] = arr[j]; 29 arr[j] = tmp; 30 } 31 32 arr[l] = arr[j]; // place the pivot to appropriate position 33 arr[j] = pivot; 34 35 return j; 36 }
Best: O(nlogn); Worst: O(n^2) Average: O(nlogn) 1.38nlogn
归并排序 (Divide and Conquer)
1 void mergeSort(int[] arr, int l, int r) { 2 if (l < r) { 3 int m = l + (r - l) / 2; 4 mergeSort(arr, l, m); // divide and conquer 5 mergeSort(arr, m + 1, r); 6 merge(arr, l, m, r); 7 } 8 } 9 10 void merge(int[] arr, int l, int m, int r) { 11 int i, j, k, n1, n2; 12 n1 = m - l + 1; // size of the left part arr 13 n2 = r - m; // size of the right part arr 14 15 int[] L = new int[n1]; // tmp arrays 16 int[] R = new int[n2]; 17 18 // copy elements to the tmp arrays 19 for (i = 0; i < n1; i++) { 20 L[i] = arr[l + i]; 21 } 22 for (j = 0; j < n2; j++) { 23 R[j] = arr[m + 1 + j]; 24 } 25 26 i = j = 0; 27 k = l; // point at the beginning position 28 29 // Copy smaller elements back to arr (overwrite) 30 while (i < n1 && j < n2) { 31 if (L[i] <= R[j]) { 32 arr[k++] = L[i++]; 33 } else { 34 arr[k++] = R[j++]; 35 } 36 } 37 38 // copy the remaining elements 39 while (i < n1) { 40 arr[k++] = L[i++]; 41 } 42 while (j < n2) { 43 arr[k++] = R[j++]; 44 } 45 }
always: O(nlogn)
堆排序 From wikipedia (Transform and Conquer)
注意,建堆过程可能与代码中不太一样,代码中数组已存在,图示中是逐渐插入过程。
基本思想:
创建堆(-->maxHeapify) --> 反复的调用del_max()
函数获取最大值
排序具体过程:
- 建立一个堆H[0..n-1]
- 把堆首(最大值)和堆尾互换
- 把堆的尺寸缩小1,并调用
shift_down(0)
,目的是把新的数组顶端数据调整到相应位置 - 重复步骤2,直到堆的尺寸为1
1 public class HeapSort { 2 private static int[] sort = new int[]{1,0,10,20,3,5,6,4,9,8,12,17,34,11}; 3 public static void main(String[] args) { 4 buildMaxHeapify(sort); // build a max heap 5 heapSort(sort); // sort the array 6 print(sort); 7 } 8 9 private static void buildMaxHeapify(int[] data){ 10 //没有子节点的才需要创建最大堆,从最后一个的父节点开始 11 int startIndex = getParentIndex(data.length - 1); 12 //从尾端开始创建最大堆,每次都是正确的堆 13 for (int i = startIndex; i >= 0; i--) { 14 maxHeapify(data, data.length, i); 15 } 16 } 17 18 /** 19 * 创建最大堆 20 * @param data 21 * @param heapSize需要创建最大堆的大小,一般在sort的时候用到,因为最多值放在末尾,末尾就不再归入最大堆了 22 * @param index当前需要创建最大堆的位置 23 */ 24 private static void maxHeapify(int[] data, int heapSize, int index){ 25 // 当前点与左右子节点比较 26 int left = getChildLeftIndex(index); 27 int right = getChildRightIndex(index); 28 29 int largest = index; 30 if (left < heapSize && data[index] < data[left]) { 31 largest = left; 32 } 33 if (right < heapSize && data[largest] < data[right]) { 34 largest = right; 35 } 36 //得到最大值后可能需要交换,如果交换了,其子节点可能就不是最大堆了,需要重新调整 37 if (largest != index) { 38 int temp = data[index]; 39 data[index] = data[largest]; 40 data[largest] = temp; 41 maxHeapify(data, heapSize, largest); // recursive to the root node 42 } 43 } 44 45 /** 46 * 排序,最大值放在末尾,data虽然是最大堆,在排序后就成了递增的 47 * @param data 48 */ 49 private static void heapSort(int[] data) { 50 //末尾与头交换,交换后调整最大堆 51 for (int i = data.length - 1; i > 0; i--) { // i--, ignore the last one element for length i 52 int temp = data[0]; 53 data[0] = data[i]; // put it to the end 54 data[i] = temp; 55 maxHeapify(data, i, 0); 56 } 57 } 58 59 /** 60 * 父节点位置 61 * @param current 62 * @return 63 */ 64 private static int getParentIndex(int current){ 65 return (current - 1) >> 1; // shift one bit to the right, equals to (current -1) / 2 66 } 67 68 /** 69 * 左子节点position注意括号,加法优先级更高 70 * @param current 71 * @return 72 */ 73 private static int getChildLeftIndex(int current){ 74 return (current << 1) + 1; 75 } 76 77 /** 78 * 右子节点position 79 * @param current 80 * @return 81 */ 82 private static int getChildRightIndex(int current){ 83 return (current << 1) + 2; 84 } 85 86 private static void print(int[] data){ 87 int pre = -2; 88 for (int i = 0; i < data.length; i++) { 89 if (pre < (int)getLog(i+1)) { 90 pre = (int)getLog(i+1); 91 System.out.println(); 92 } 93 System.out.print(data[i] + " |"); 94 } 95 } 96 97 /** 98 * 以2为底的对数 99 * @param param 100 * @return 101 */ 102 private static double getLog(double param){ 103 return Math.log(param)/Math.log(2); 104 } 105 }
avg: O(nlogn) <= 2nlogn 在随机文件上时间测试的结果显示heapsort比快速排序要慢,但是可以比得上mergeSort. (来源于算法设计与分析基础 2rd Edition P228)
选择排序
1 void selectSort(int[] arr){ 2 int min; 3 for(int i = 0;i<arr.length-1;i++){ 4 min = i; 5 for(int j=i;j<arr.length;j++){ 6 if(arr[min] > arr[j]){ // find the smallest elements 7 min = j; 8 } 9 } 10 int tmp = arr[min]; 11 arr[min] = arr[i]; 12 arr[i] = tmp; 13 } 14 }
Best: O(n^2); Worst: O(n^2) Average: O(n^2)
冒泡排序
void bubbleSort(int[] arr){ for(int i=0;i<arr.length;i++){ for(int j = arr.length-1; j > i; j--){ if(arr[j] < arr[j-1]){ // find the smallest through bubble int tmp = arr[j]; arr[j] = arr[j-1]; arr[j-1] = tmp; } } } }
Best: O(n^2); Worst: O(n^2) Average: O(n^2)
此代码实现的最佳时间消耗 O(n^2), 若要使最佳情况时间开销达到O(n),可以设置一个sentinel实现:当不发生交换的时候就break退出,完成了排序。
插入排序
1 void insertSort(int[] arr) { 2 if (arr.length < 2) { 3 return; 4 } 5 6 int i, j; 7 int temp; 8 for (i = 1; i < arr.length; i++) { 9 temp = arr[i]; 10 for (j = i - 1; j >= 0 && arr[j] > temp; j--) { // in-place 11 arr[j + 1] = arr[j]; // shift 12 } 13 arr[j + 1] = temp; 14 } 15 }
Best: O(n); Worst: O(n^2) Average: O(n^2)
Java工具类排序
java.util.Arrays.sort(arr);
- The sorting algorithm is a Dual-Pivot Quicksort by Vladimir Yaroslavskiy, Jon Bentley, and Joshua Bloch. 比上面的快速排序更快。
计算Java程序运行时间:
1 long startTime = System.nanoTime(); 2 //code 3 long endTime = System.nanoTime(); 4 System.out.println("Took "+(endTime - startTime) + " ns");
Test It:
1 public static void main(String[] args) { 2 int arr[] = {1, 3, 1, 8, 4, 8,3,23,2}; 3 Main main = new Main(); 4 long startTime = System.nanoTime(); 5 // main.quickSort(arr, 0, arr.length - 1); 6 // main.selectSort(arr); 7 // main.insertSort(arr); 8 // main.bubbleSort(arr); 9 10 //code 11 long endTime = System.nanoTime(); 12 System.out.println("Took "+(endTime - startTime) + " ns"); 13 for (int a : arr) { 14 System.out.println(a); 15 } 16 }