归并排序
前言:在博客写这些文章的目的用于记录所学,怕以后忘了,如果哪里写的不对欢迎指正,谢谢!!
学习目标:掌握归并排序算法的原理和思想
一、前提知识
排序算法概念、时间复杂度。可前往此网址 排序算法学习01_算法基础介绍阅读
二、归并排序介绍
归并排序算法,采用分治思想排序元素。它和快速排序类似,递归地对序列一分为二进行排序。
三、归并排序工作原理
归并排序也是递归地对序列一分为二进行排序。但它不像快排,边分边排。而是一直进行分解,分解到某个子序列只剩一个元素时,然后递归返回,才开始排序,这也是归并名字的由来。
它也不像是快排在原有的基础上进行排序,而是通过申请一个临时空间(数组),该空间存放的是每次递归时中序列里的元素,我们就要在这个临时空间上比较元素,然后依次从小到大的(升序时)赋值到原数组。
先对原序列的一部分子序列排序,再把这几个部分子序列合起来再排序。递归地执行,最终返回一个排序好的序列。
如果你学过快速排序,那么我相信你可以很快学会归并排序。
四、归并排序设计思路
使用递归解决分治策略,主要思考以下两点:
- 分解:如何对序列进行划分,也就是如何分组,分成什么样
- 归并:如何对分组后的数据进行组合排序
-
关于第一点,序列如何分组
- 归并排序采用相邻分组,也就是划分组的方式,就相邻位置的元素即可为一组
- 关于具体分组方式,接下来就要引入三个变量
- startIndex:用于表示某个序列的起始位置
- endIndex:用于表示某个序列最后位置
- middleIndex:中部下标,由 startIndex + (endIndex - startIndex) / 2 所得。该变量有两层作用
- 第一层:递归过去时,用于分离序列(分组),分解序列依靠该变量
- 第二层:递归回来要排序时,将用于区分要进行合并排序的两个子序列的位置
-
关于第二点,那就是排序的问题
-
这时,就要用到临时空间
- 临时空间首先存放子序列的元素,子序列就是从startIndex到endIndex位置范围内的元素
-
归并:归并的是什么?通过介绍我们知道,每次递归的对序列一分为二。那递归回来时,就要对这被一分为二的两个序列进行整合排序,这就是归并
-
接下来需再引入两个变量
- left:刚开始指向左边子序列的首位下标
- right:刚开始指向右边子序列的首位下标
-
这两个变量将在临时空间上移动,如何区分临时空间上的左右序列,这时派上middleIndex的用场了(刚刚讲过第二层作用)
- left~middleIndex为左序列
- middleIndex+1~right为右序列
-
再接下来就是在临时空间上两边子序列比较的问题了(采用升序)
- 首先遍历tempArr(临时空间)
- 然后取tempArr中左子序列left指向的元素与右子序列right指向的元素比较
- 大于时,则把right位置指向元素放入原序列,原序列的索引如何取
- 取遍历tempArr时的下标,这就可以起到,在子序列进行排序的作用
- 哪个子序列往原序列放入元素,那么该子序列上的扫描下标,则往后移动一步
- 此时,我们的right就要*+1*,以判断右子序列的下个值
- 重复此逻辑
- 还有两种特殊情况
- 左子序列left在移动过程中,越过中部下标middleIndex
- 右子序列right在移动过程中,越过endIndex,也就是tempArr的最后一个元素
- 这两种情况代表了,所在的子序列以判断完毕,可以直接把另一边序列的元素依次填入原序列
- 图解的话大概情况如这样,就不细话了(具体情况可以看其他博主用图解将的归并)。这是一个临时空间
- 首先遍历tempArr(临时空间)
-
-
五、代码实现
package com.migu.sortingAlgorithm;
import java.util.Arrays;
/**
* 归并排序
*/
public class MergeSort {
public static void main(String[] args) {
int array[] = {3,2,11,5,1,7,9,4,-1,-8,0};
sort(array);
System.out.println(Arrays.toString(array)); // 调用工具类输出
}
public static void sort(int[] arr) {
int[] tempArr = new int[arr.length]; // 先申请一个临时空间,用于存放每次归并好的序列
sort(arr,tempArr,0,arr.length-1);
}
/**
* 采用分治思想,以递归方式
* @param arr 要排序数组
* @param tempArr 临时空间
* @param startIndex 子序列起始位置
* @param endIndex 子序列末尾位置
*/
private static void sort(int[] arr,int[] tempArr,int startIndex,int endIndex){
if(endIndex <= startIndex){
return;
}
// 中部下标,依靠该值递归的一分为二分解子序列
int middleIndex = startIndex + (endIndex - startIndex) / 2;
// 分解
sort(arr,tempArr,startIndex,middleIndex);
sort(arr,tempArr,middleIndex + 1,endIndex);
// 归并(排序)
mergeSort(arr,tempArr,startIndex,middleIndex,endIndex);
}
/**
* 归并
* @param arr 要排序数组
* @param tempArr 临时空间
* @param startIndex 归并起始位置
* @param middleIndex 划分左右序列
* @param endIndex 归并终止位置
*/
private static void mergeSort(int[] arr,int[] tempArr,int startIndex,int middleIndex,int endIndex) {
// 复制要合并的数据到临时空间
for (int s = startIndex; s <= endIndex; s++) {
tempArr[s] = arr[s];
}
int left = startIndex;// 左边首位下标,在左子序列扫描,向右移动
int right = middleIndex + 1;// 右边首位下标,在右子序列扫描,向右移动
// 遍历临时空间
for (int k = startIndex; k <= endIndex; k++) {
// 如果左子序列下标在移动过程中大于中部下标,证明左边的数据已经排完了。
if(left > middleIndex){
// 那么只需把右子序列元素一直填入原序列
arr[k] = tempArr[right++];
// 右边同理
} else if (right > endIndex){
arr[k] = tempArr[left++];
} else if (tempArr[right] < tempArr[left]){ // 实现升序 '<'
arr[k] = tempArr[right++];// 将右子序列值排入
} else {
arr[k] = tempArr[left++];// 将左子序列值排入
}
}
}
}
六、时间复杂度
该算法的时间复杂度为 O(nlogn) ,区别于之前所讲的算法,归并排序算法还需要一个额外的空间,O(1)
七、总结
归并排序算法,采用分治思想,对序列一直进行一分为二的分解,然后分解到最后,回来时再进行合并排序,是一种十分高效的排序算法。且他是稳定的
网上所说这是利用一种什么二叉树来排序,我还没学过,就不细讲了,哎呀我好菜,如果你能一步一步看到这里,真的万分感谢,最近学快排和归并头疼死我了