首页 > 代码库 > 排序算法Java版,以及各自的复杂度,以及由堆排序产生的top K问题
排序算法Java版,以及各自的复杂度,以及由堆排序产生的top K问题
常用的排序算法包括:
- 冒泡排序:
每次在无序队列里将相邻两个数依次进行比较,将小数调换到前面, 逐次比较,直至将最大的数移到最后。最将剩下的N-1个数继续比较,将次大数移至倒数第二。依此规律,直至比较结束。时间复杂度:O(n^2) - 选择排序:
每次在无序队列中“选择”出最大值,放到有序队列的最后,并从无序队列中去除该值(具体实现略有区别)。时间复杂度:O(n^2) - 直接插入排序:
始终定义第一个元素为有序的,将元素逐个插入到有序排列之中,其特点是要不断的 移动数据,空出一个适当的位置,把待插入的元素放到里面去。时间复杂度:O(n^2) - 希尔排序:
希尔排序是对直接插入排序的改进,希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。 - 快速排序:
递归地将数组一分为2,左边是所有比第一个数小的,右边是所有比第一个数大的。这其中涉及到如何移动交换的技巧问题。本题为挖坑填坑模式。时间复杂度:O(nlogn) - 堆排序:
对需要排序的数组首先建立堆,然后(如果最终是由小到大排序)依次交换根位置与最后一个节点位置的数值,之后调整堆,循环往复。时间复杂度:O(nlogn)。关于堆这一数据结构以及堆排序可以参考百度百科:堆排序 - 归并排序:给定两个已经排好序(由小到大)的数组,归并排序的意思是,选取数组A中的第一个值,与另一个数组B中的第一个值比较,每找到一个较小的值就写入新的数组C,直到所有的值都大于此值时,将此值写入C,选取数组A的一个值作为对照值,重复上面的操作。时间复杂度:O(nlogn)
关于:各种排序算法时间复杂度和空间复杂度表
关于各种排序算法的稳定性:
1) 稳定的:如果存在多个具有相同排序码的记录,经过排序后,这些记录的相对次序仍然保持不变,则这种排序算法称为稳定的。
插入排序、冒泡排序、归并排序、分配排序(桶式、基数)都是稳定的排序算法。
2) 不稳定的:否则称为不稳定的。
直接选择排序、堆排序、shell排序、快速排序都是不稳定的排序算法。
关于不同引用场景下使用不同的算法:
(1) 若n较小(如n≤50),可采用直接插入或直接选择排序。
当记录规模较小时,直接插入排序较好;否则因为直接选择移动的记录数少于直接插人,应选直接选择排序为宜。
(2) 若文件初始状态基本有序(指正序),则应选用直接插人、冒泡或随机的快速排序为宜;
(3) 若n较大,则应采用时间复杂度为O(nlgn)的排序方法:快速排序、堆排序或归并排序。
快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况。这两种排序都是不稳定的。
若要求排序稳定,则可选用归并排序。但本章介绍的从单个记录起进行两两归并的 排序算法并不值得提倡,通常可以将它和直接插入排序结合在一起使用。先利用直接插入排序求得较长的有序子文件,然后再两两归并之。因为直接插入排序是稳定 的,所以改进后的归并排序仍是稳定的。
补充:视觉直观感受 7 种常用的排序算法
代码1:冒泡排序,选择排序,直接插入排序,希尔排序,快速排序代码。
1 /** 2 * 冒泡排序: 每次在无序队列里将相邻两个数依次进行比较,将小数调换到前面, 逐次比较,直至将最大的数移到最后。 3 * 最将剩下的N-1个数继续比较,将次大数移至倒数第二位。 依此规律,直至比较结束。 4 */ 5 public static void bubbleSort(int[] a) { 6 int n = a.length; 7 for (int i = 0; i < n - 1; i++) { 8 for (int j = 0; j < n - i - 1; j++) { 9 if (a[j] > a[j + 1]) { 10 int temp = a[j + 1]; 11 a[j + 1] = a[j]; 12 a[j] = temp; 13 } 14 } 15 } 16 } 17 18 /** 19 * 选择排序:每次在无序队列中“选择”出最大值,放到有序队列的最后,并从无序队列 中去除该值(具体实现略有区别)。 20 */ 21 public static void selectSort(int[] a) { 22 int n = a.length; 23 int max_index = 0; 24 for (int i = n - 1; i >= 0; i--) { 25 max_index = i; 26 for (int j = 0; j <= i; j++) { 27 if (a[j] > a[max_index]) 28 max_index = j; 29 } 30 if (max_index != i) { 31 int temp = a[i]; 32 a[i] = a[max_index]; 33 a[max_index] = temp; 34 } 35 } 36 } 37 38 /** 39 * 插入排序:始终定义第一个元素为有序的,将元素逐个插入到有序排列之中,其特点是要不断的 移动数据,空出一个适当的位置,把待插入的元素放到里面去。 40 */ 41 public static void insertSort(int[] a) { 42 int n = a.length; 43 int j = 0; 44 for (int i = 1; i < n; i++) { 45 j = i - 1; 46 int temp = a[i]; 47 while (j >= 0 && a[j] > temp) { 48 a[j + 1] = a[j]; 49 j--; 50 } 51 a[j + 1] = temp; 52 for (int m : a) { 53 System.out.print(m + ","); 54 } 55 System.out.println(); 56 // for (int j = i; j > 0; j--) { 57 // if (a[j] < a[j - 1]) { 58 // int temp = a[j]; 59 // a[j] = a[j - 1]; 60 // a[j - 1] = temp; 61 // } 62 // } 63 } 64 } 65 66 /** 67 * 快速排序:递归地将数组一分为2,左边是所有比第一个数小的,右边是所有比第一个数大的 68 */ 69 public static void quickSort(int[] a) { 70 quickSort(a, 0, a.length - 1); 71 } 72 73 private static void quickSort(int[] a, int l, int r) { 74 if (l < r) { 75 // 取左边第一个值作为分界值 76 int mid = a[l]; 77 int i = l, j = r; 78 while (i < j) { 79 while (i < j && a[j] > mid) 80 j--; 81 if (i < j) 82 a[i++] = a[j]; 83 while (i < j && a[i] <= mid) 84 i++; 85 if (i < j) 86 a[j--] = a[i]; 87 } 88 a[i] = mid; 89 quickSort(a, l, i - 1); 90 quickSort(a, i + 1, r); 91 } 92 } 93 94 /** 95 * 希尔排序是对直接插入排序的改进,希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多, 96 * 当增量减至1时,整个文件恰被分成一组,算法便终止。 97 * 98 * @param a 99 */ 100 public static void shellSort(int[] a) { 101 int d = a.length; 102 while (true) { 103 d = d / 2; 104 for (int x = 0; x < d; x++) {// 分组 105 // 对每组进行直接插入排序 106 for (int i = x + d; i < a.length; i = i + d) { 107 int temp = a[i];// 记录要插入的值 108 int j; 109 // 将要插入的值插入到正确位置,这一过程中不断检测前面的数,如果相比较大,则同时移动一次 110 for (j = i - d; j >= 0 && a[j] > temp; j = j - d) { 111 a[j + d] = a[j]; 112 } 113 a[j + d] = temp; 114 } 115 } 116 if (d == 1) { 117 break; 118 } 119 } 120 }
代码2:推排序(由于java版比较长,所以另写一个)
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 4 public static void main(String[] args) { 5 System.out.println(sort.length); 6 buildMaxHeapify(sort); 7 heapSort(sort); 8 print(sort); 9 } 10 11 private static void buildMaxHeapify(int[] data) { 12 // 没有子节点的才需要创建最大堆,从最后一个的父节点开始 13 int startIndex = getParentIndex(data.length - 1); 14 // 从尾端开始创建最大堆,每次都是正确的堆 15 for (int i = startIndex; i >= 0; i--) { 16 maxHeapify(data, data.length, i); 17 } 18 } 19 20 /** 21 * 创建最大堆 22 * 23 * @paramdata 24 * @paramheapSize需要创建最大堆的大小,一般在sort的时候用到,因为最多值放在末尾,末尾就不再归入最大堆了 25 * @paramindex当前需要创建最大堆的位置 26 */ 27 private static void maxHeapify(int[] data, int heapSize, int index) { 28 // 当前点与左右子节点比较 29 int left = getChildLeftIndex(index); 30 int right = getChildRightIndex(index); 31 32 int largest = index; 33 if (left < heapSize && data[index] < data[left]) { 34 largest = left; 35 } 36 if (right < heapSize && data[largest] < data[right]) { 37 largest = right; 38 } 39 // 得到最大值后可能需要交换,如果交换了,其子节点可能就不是最大堆了,需要重新调整 40 if (largest != index) { 41 int temp = data[index]; 42 data[index] = data[largest]; 43 data[largest] = temp; 44 maxHeapify(data, heapSize, largest); 45 } 46 } 47 48 /** 49 * 排序,最大值放在末尾,data虽然是最大堆,在排序后就成了递增的 50 * 51 * @paramdata 52 */ 53 private static void heapSort(int[] data) { 54 // 末尾与头交换,交换后调整最大堆 55 for (int i = data.length - 1; i > 0; i--) { 56 int temp = data[0]; 57 data[0] = data[i]; 58 data[i] = temp; 59 maxHeapify(data, i, 0); 60 } 61 } 62 63 /** 64 * 父节点位置 65 * 66 * @paramcurrent 67 * @return 68 */ 69 private static int getParentIndex(int current) { 70 return (current - 1) >> 1; 71 } 72 73 /** 74 * 左子节点position注意括号,加法优先级更高 75 * 76 * @paramcurrent 77 * @return 78 */ 79 private static int getChildLeftIndex(int current) { 80 return (current << 1) + 1; 81 } 82 83 /** 84 * 右子节点position 85 * 86 * @paramcurrent 87 * @return 88 */ 89 private static int getChildRightIndex(int current) { 90 return (current << 1) + 2; 91 } 92 93 private static void print(int[] data) { 94 int pre = -2; 95 for (int i = 0; i < data.length; i++) { 96 if (pre < (int) getLog(i + 1)) { 97 pre = (int) getLog(i + 1); 98 System.out.println(); 99 } 100 System.out.print(data[i] + "|"); 101 } 102 } 103 104 /** 105 * 以2为底的对数 106 * 107 * @paramparam 108 * @return 109 */ 110 private static double getLog(double param) { 111 return Math.log(param) / Math.log(2); 112 } 113 }
代码3:top-K问题
1 /** 2 * 使用最大堆进行top K的选取 3 */ 4 public static int[] topK(int[] a, int k) { 5 int[] res = new int[k]; 6 if (k > a.length) { 7 System.err.println("k larger than length of array"); 8 return res; 9 } 10 for (int i = 0; i < a.length; i++) { 11 if (a[i] > res[0]) { 12 res[0] = a[i]; 13 for (int j = 0; j < k && 2 * j + 1 < k;) { 14 int tag1 = 0, tag2 = 0; 15 if (2 * j + 2 < k && res[2 * j + 1] > res[2 * j + 2]) { 16 if (res[j] > res[2 * j + 2]) { 17 int temp = res[2 * j + 2]; 18 res[2 * j + 2] = res[j]; 19 res[j] = temp; 20 j = 2 * j + 2; 21 tag1 = 1; 22 } 23 } else { 24 if (res[j] > res[2 * j + 1]) { 25 int temp = res[2 * j + 1]; 26 res[2 * j + 1] = res[j]; 27 res[j] = temp; 28 j = 2 * j + 1; 29 tag2 = 1; 30 } 31 } 32 if (tag1 == 0 && tag2 == 0) 33 break; 34 } 35 } 36 } 37 return res; 38 }
排序算法Java版,以及各自的复杂度,以及由堆排序产生的top K问题