时间复杂度为O(nlogn)的排序算法(归并排序、快速排序),比时间复杂度O(n²)的排序算法更适合大规模数据排序。java
采用“分治思想”,将要排序的数组从中间分红先后两个部分,而后对先后两个部分分别进行排序,再将排序好的两部分合并在一块儿,这样数组就有序了。算法
分治是一种解决问题的思想,递归是一种编程技巧,使用递归的技巧就是,先找到递归公式和终止条件,而后将递归公式翻译成递归代码。编程
//递归公式 merge_sort(p...r) = mege(merge_sort(p...q),merge_sort(q+1,r)); //终止条件 p >= r,再也不继续分解
public class MergeSort { public static void main(String[] args) { int[] a = {4, 3, 2, 1, 6, 5}; mergeSort(a,0,a.length - 1); for (int i : a) { System.out.println(i); } } public static void mergeSort(int[] a, int p, int r) { //终止条件 if (p >= r) return; int q = (r - p) / 2 + p; //递归公式 mergeSort(a, 0, q); mergeSort(a, q + 1, r); //到这里递归结束,能够假设[0,q],[q + 1,r]已经排好序了 merge(a, p, q, r); } private static void merge(int[] a, int p, int q, int r) { int i = p; int j = q + 1; int k = 0; int[] temp = new int[r - p + 1]; while (i <= q && j <= r) { if (a[i] <= a[j]) { temp[k++] = a[i++]; } else { temp[k++] = a[j++]; } } //判断哪一个子数字中有数据,判断依据必须是 <= int start = i; int end = q; if (j <= r) { start = j; end = r; } //将剩余数据拷贝到临时数组temp中 while (start <= end) { temp[k++] = a[start++]; } //将temp数组中数据[0,r-p],拷贝至a数组中原来位置 //能够直接使用数组复制函数 for (int n = 0; n <= r - p; n++) { a[p + n] = temp[n]; } } }
能够利用哨兵节点对merge方法进行优化,将数组分配两部分,并将Integer.MAX_VALUE添加到每一个数组的最后一位,就能够一次性将两个数组中数据所有比较完,不会剩余数据数组
//优化merge代码 private static void mergeBySentry(int[] a, int p, int q, int r) { int[] leftArr = new int[q - p + 2]; int[] rightArr = new int[r - q + 1]; for (int i = 0; i <= q - p; i++) { leftArr[i] = a[p + i]; } leftArr[q - p + 1] = Integer.MAX_VALUE; for (int i = 0; i < r - q; i++) { rightArr[i] = a[q + i + 1]; } rightArr[r - q] = Integer.MAX_VALUE; int i = 0; int j = 0; int k = p; while (k <= r) { if (leftArr[i] <= rightArr[j]) { a[k++] = leftArr[i++]; } else { a[k++] = rightArr[j++]; } } }
归并排序是稳定的排序算法,是否稳定取决于合并merge方法,当两个数组有相同数据合并时,能够先将左边的数据先存入temp中,这样就能够保证稳定性函数
最好状况、最坏状况,仍是平均状况,时间复杂度都是 O(nlogn)。推导过程待补充...性能
归并排序不是原地排序算法。优化
递归代码的空间复杂度并不能像时间复杂度那样累加。刚刚咱们忘记了最重要的一点,那就是,尽管每次合并操做都须要申请额外的内存空间,但在合并完成以后,临时开辟的内存空间就被释放掉了。在任意时刻,CPU 只会有一个函数在执行,也就只会有一个临时的内存空间在使用。临时内存空间最大也不会超过 n 个数据的大小,因此空间复杂度是 O(n)ui
对数组p到r进行排序,从数组中从中取出一个数据做为pivot(分区点),将小于pivot的放在左边,大于pivot的放在右边,以后利用分治、递归思想,再对左右两边的数据进行排序,直到区间缩小为1,说明数据有序了翻译
//递归公式 quick_sort(p...r) = quick_sort(p...q) + quick_sort(q + 1 ... r) //终止条件 p >= r
public static void quickSort(int[] a, int n){ quickSortInternally(a,0,n - 1); } private static void quickSortInternally(int[] a,int p, int r){ if (p >= r) return; int q = partition(a, p, r); quickSortInternally(a,p,q - 1); quickSortInternally(a,q + 1,r); } //p:起始位置,r:终止位置 private static int partition(int[] a, int p, int r) { //取出中间点 int pivot = a[r]; //i、j为双指针,i始终指向大于中间点的第一个元素,j不断遍历数组,最终指向最后一个元素即中间点 int i = p; //比较从p开始,到r-1结束 for(int j = p; j < r; ++j) { //若是小于中间点 if (a[j] < pivot) { if (i == j) { //若是i和j相等,说明以前没有大于中间点的元素,i和j都加1 // j在进行下一轮循环的时候会自动加1,因此在这里只加i ++i; } else { //若是不相等,说明i已经指向第一个大于中间点的元素 // 须要将小于中间的的a[j]与a[i]交换位置,而后都加1 int tmp = a[i]; a[i++] = a[j]; a[j] = tmp; } } } //循环结束,i指向大于中间点a[r]的第一个元素 //将a[i]与a[r]交换位置 int tmp = a[i]; a[i] = a[r]; a[r] = tmp; System.out.println("i=" + i); //返回交换后中间点坐标位置 return i; }
快速排序是原地、不稳定的排序算法,时间复杂度在大部分状况下的时间复杂度均可以作到 O(nlogn),只有在极端状况下,才会退化到 O(n²)指针
原地:空间复杂度为O(1),不须要占用额外存储空间
不稳定:由于分区的过程涉及交换操做,若是数组中有两个相同的元素,好比序列 6,8,7,6,3,5,9,4,在通过第一次分区操做以后,两个 6 的相对前后顺序就会改变。因此,快速排序并非一个稳定的排序算法
时间复杂度:待补充
O(n) 时间复杂度内求无序数组中的第 K 大元素。好比,4, 2, 5, 12, 3 这样一组数据,第 3 大元素就是 4。
思路:
选择数组A[0,n-1]的最后一个元素A[n-1]做为中间点pivot
对数组A[0,n-1]原地分区,分为[0,p-1],[p],[p+1,n-1],此时[0,p-1]这个分区中虽然可能无序,可是所有是比中间点小的元素,因此[p]为这群数中的第p+1大元素(下标为p,因此共有p+1个元素,应该是p+1大)
比较p+1和K,若是p+1 = K,说明A[p]就是求解元素,若是K > p+1,说明求解元素出如今A[p+1,n-1]中,则按照上面方法递归对A[p+1,n-1]进行分去查找,同理,若是K < p+1,则对A[0,p-1]进行分区查找
时间复杂度:O(n)。第一次分区查找,咱们须要对大小为 n 的数组执行分区操做,须要遍历 n 个元素。第二次分区查找,咱们只须要对大小为 n/2 的数组执行分区操做,须要遍历 n/2 个元素。依次类推,分区遍历元素的个数分别为、n/二、n/四、n/八、n/16.……直到区间缩小为 1。若是咱们把每次分区遍历的元素个数加起来,就是:n+n/2+n/4+n/8+…+1。这是一个等比数列求和,最后的和等于 2n-1。因此,上述解决思路的时间复杂度就为 O(n)。
笨方法:每次取数组中的最小值,将其移动到数组的最前面,而后在剩下的数组中继续找最小值,以此类推,执行 K 次,也能够找到第K大元素。但这种方法的时间复杂度为O(K*n),在K值比较小时,时间复杂度为O(n),当K为n/2或n时,时间复杂度就为O(n²)了
如今你有 10 个接口访问日志文件,每一个日志文件大小约 300MB,每一个文件里的日志都是按照时间戳从小到大排序的。你但愿将这 10 个较小的日志文件,合并为 1 个日志文件,合并以后的日志仍然按照时间戳从小到大排列。若是处理上述排序任务的机器内存只有 1GB,你有什么好的解决思路,能“快速”地将这 10 个日志文件合并吗