归并排序(分治思想的运用)
原理:将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的,然后再把有序的子序列合并为整天有序序列。
分治算法步骤:
第一步:划分。将原问题划分为几个子问题
第二步:递归求解。递归求解每个子问题
第三步:合并。将求解后的子问题合并成原问题
代码实现(递归):
void mergeSort(int[] arr){ sort(arr,0,arr.length-1); } void sort(int[] arr, int left, int right){ if(left >= right){return;} //找出中间索引 int middle = left + (right - left) / 2; //对左边数组进行排序 sort(arr,left,middle); ////对右边数组进行排序 sort(arr,middle + 1,right); //合并 merge(arr,left,middle,right); print(arr); } /*将两个数组进行归并,归并前两个数组有序,归并后依然有序 参数列表:arr:数组对象。left:左数组的第一个元素索引。middle:左数组的最后一个元素索引。right:右数组的最后一个元素索引*/ void merge(int[] arr, int left, int middle, int right){ //临时数组 int[] tmpArr = new int[arr.length]; //临时数组的索引 int tmpArrIndex = left; //右数组的第一个索引 int rightFirst = middle + 1; //缓存数组第一个元素的索引 int tmp = left; while(left <= middle && rightFirst <= right){ //从两个子数组中取出最小的放临时数组中 if(arr[left] <= arr[rightFirst]){ tmpArr[tmpArrIndex++] = arr[left++]; }else{ tmpArr[tmpArrIndex++] = arr[rightFirst++]; } } //剩余部分依次放入临时数组 while(left <= middle){ tmpArr[tmpArrIndex++] = arr[left++]; } while(rightFirst <= right){ tmpArr[tmpArrIndex++] = arr[rightFirst++]; } //将临时数组的内容拷贝回原数组中 while(tmp <= right){ arr[tmp] = tmpArr[tmp++]; } } void print(int[] arr){ for(int i: arr){ System.out.print(i + "\t"); } }
分析:算法稳定,空间复杂度【数组O(n+logn)logn为递归时使用的深度为logn的栈空间,链表O(logn)】,时间复杂度【最好、平均、最坏均为O(nlogn)】
代码实现(非递归):
void mergeSortNonRecursive(int[] arr){ if(arr == null || arr.length <= 1){return;} //当前的归并的子序列长度为1 int len = 1; while(len <= arr.length){ //每次从数组第一个元素开始,每经过len*2个元素对相邻子序列进行归并,并注意对归并长度段为奇数时以及最后一个归并段与前面不等时的处理 for(int i=0; i+len <= arr.length - 1; i += len * 2){ //左数组的第一个元素索引 int left = i; //左数组的最后一个元素索引 int mid = i + len -1; //右数组的最后一个元素索引 int right = i + len * 2 -1; //边界处理 if(right > arr.length - 1){right = arr.length -1;} //merge参考递归排序的merge merge(arr,left,mid,right); } len *= 2; } return; }
分析:在空间上避免了递归时使用的深度为O(logn)的栈空间,在时间上,避免了递归,在时间上也有所提升。