1. 归并排序:归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2路归并。
2. 归并排序的时间,空间复杂度及稳定性:
1>. 时间复杂度:O(nlogn);因为我们这里归并排序的过程中向下递归二分数组时的时间复杂度为O(logn);但是再向上并的过程中,还会遍历原数组,时间复杂度为:O(n);因此归并排序的时间复杂度为:O(nlogn)。
2>. 空间复杂度:S(n);因为我们在归并排序时创建了其他新的数组。
3>. 稳定性: 稳定;因为我们在归并排序时,并不会出现大跨度的交换元素。
3. 我们可以创建一个父类:父类中封装数组的创建,排序,交换位置等方法,其他的排序方法的类都继承于这个父类,重写父类中的方法。
public abstract class Sort {
public int[] arr;
public Sort(){};
//有参构造方法 创建一个数组的副本
public Sort(int[] arr){
this.arr = new int[arr.length];
for(int i = 0; i < arr.length; i++){
this.arr[i] = arr[i];
}
}
//对数组进行排序
public abstract void sort(int[] arr);
//交换数据位置
public void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
4. 归并排序的过程:
//归并排序 时间复杂度为:O(nlogn) 空间复杂度为:S(n) 算法稳定性:稳定
/*
我们先将数组通过递归向下二分 直至数组的长度为1
二分结束后 再将数组向上合并
合并时 我们要创建一个新的数组即原数组的副本 通过原数组和新数组下标的映射 来将新数组进行有序的合并
*/
public class MergeSort extends Sort{
public MergeSort(int[] arr){
super(arr);
}
@Override
public void sort(int[] arr) {
mergeSort(arr, 0, arr.length - 1);
System.out.println(Arrays.toString(arr)); //打印排序后的数组
}
//将数组进行归并排序
//L表示原数组的左端下标 R表示原数组的右端下标
private void mergeSort(int[] arr, int L, int R) {
if(L >= R){ //向下递归的临界条件 当L>=R时 就向上合并
return;
}
int mid = (L + R) / 2; //表示数组的中间位置
//向下对数组进行二分
mergeSort(arr, L, mid);
mergeSort(arr, mid + 1, R);
if(arr[mid] > arr[mid + 1]){
merge(arr, L, mid, R); //向上合并
}
}
//将子数组向上进行合并
/*
我们这里L R i j k这些指针都指向的是原数组 而新数组的下标索引是通过原数组中的索引的关系来对应的
i-l / j-l表示在新数组aux中的位置
* 因为我们原数组或子数组的索引开始位置不一定就是从0开始的
* 虽然我们将最开始的数组递归分半 但是分半后的数组的下标依然对应的是原数组的下标
* 因此我们在合并数组时 原数组或子数组的索引开始位置不一定就是从0开始的
* 所以新数组的元素对应的下标就为i-l / j-l
*/
//l表示数组的左边边界 r表示数组的右边边界 mid表示数组的中间位置
private void merge(int[] arr, int L, int mid, int R) {
int[] aux = new int[R - L + 1];
for(int k = L; k <= R; k++){
aux[k - L] = arr[k];
}
int i = L; //i表示左边子数组的开始位置
int j = mid + 1; //j表示右边子数组的开始位置
for(int k = L; k <= R; k++){ //遍历原数组
if(i > mid){
/*
判断右边子数组是否已经遍历结束 如果已经结束 就将左边子数组的剩余数据依次填到原数组对应的位置即可
因为这时我们左右子数组中的数据已经是有序的了
*/
arr[k] = aux[j - L];
j++;
}else if(j > R){
/*
判断左边子数组是否已经遍历结束 如果已经结束 就将右边子数组的剩余数据依次填到原数组对应的位置即可
因为这时我们左右子数组中的数据已经是有序的了
*/
arr[k] = aux[i - L];
i++;
}else if(aux[i - L] < aux[j - L]){
/*
如果左边子数组的数据小于右边子数组的数据
就将小的数据填入到原数组中
左边子数组指针向后移
*/
arr[k] = aux[i - L];
i++;
}else{
/*
如果右边子数组的数据小于左边子数组的数据
就将小的数据填入到原数组中
右边子数组指针向后移
*/
arr[k] = aux[j - L];
j++;
}
}
}
}
5. 测试类:测试类中只测试排序方法是否能成功。
public class TestSort {
public static void main(String[] args) {
int[] arr = {9, 5, 4, 6, 7, 2, 1, 3, 8, 10};
MergeSort mergeSort = new MergeSort(arr);
mergeSort.sort(arr);
}
}