参考链接: Java中将数组合并为的新数组
归并排序
大家好,这是我第一次在CSDN上写东西,因为我一直觉得有需要就去找别人的blog看看就好,但我发现自己写出来的东西确实能加深记忆。我半路出家,属实是个菜鸟,文章也许写的会有很多问题,还望大家多多包涵,欢迎指正。 最近在学数据结构,数据结构作为程序员该有的基本内功,无疑是我们要多加练习的。然而最为菜鸟的我,在学习的过程中也发现很多坑在大佬眼里不就是一句话的事 ,我写的这些既是为了帮助有需要的人,也是对自己的锻炼以及记录。废话到此结束,再多说要被锤了。
代码打头
~~废话不多说先上代码,如果代码都跑不出,大家就可以散了。~~
import java.util.Arrays;
public class mergeSortDemo {
public static void main(String[] args) {
int arr[] = new int[10];
for (int i = 0;i<arr.length;i++){
arr[i] = (int) (Math.random()*100);
}
for (int i=0;i<arr.length;i++){
System.out.print(arr[i]+" ");
}
System.out.println("~~~~~~~~~~");
mergeSort(arr,0,9);
}
public static void merge(int arr[],int low,int mid,int high){
int i = low;
int j = mid+1;
int t = 0;
int temp[] = new int[high-low+1];
while (i<=mid && j<=high){
if (arr[i]<arr[j]){
temp[t++] = arr[i++];
}
else {
temp[t++] = arr[j++];
}
}
//
while (i<=mid){
temp[t++] = arr[i++];
}
while (j<=high){
temp[t++] = arr[j++];
}
//
for (int tempLeft=0;tempLeft<temp.length;tempLeft++){
arr[low+tempLeft] = temp[tempLeft];
}
}
public static void mergeSort(int arr[],int low,int high){
int mid = (low+high)/2;
if (low<high) {
mergeSort(arr, low, mid);
mergeSort(arr, mid+1, high);
//
merge(arr,low,mid,high);
System.out.println( Arrays.toString(arr));
}
}
}
是不是被这精妙的逻辑给迷住了。 何谓归并排序,归并排序就是divide-and-merge。
如图,算法的基本做法是:先分割数字,再按照每组的的大小排序,两个小组变为中组,两个中组合为大组。
整体思路
归并排序首先需要将数组拆分,然后治之。具体为,将一串数组分为两半,再各自对两半继续拆分,直至每组的的元素个数为一。此时开始治:如图中将数据分到最后一步,则上层长度为2,用当前的两个数组,按照算法来排序整合merge(){① 比较两个数组中的每一个数,将当前索引指向的较小的数装入临时数组temp中;② 当一组数据全部装入temp中时,一定会出现一种情况,另一组一定还有数据没存进去,所以将剩下的数存入temp;③ 这是比较难想到的一点:存入temp后,还需要返回到原先数组arr【】中去。但注意,虽然每次都是返回去的下标都是从0—>length-1,但不是一次性的。因为整个排序不是一次排好,每次小组内排完就需要存回arr,由此可知,不可能只用回传一次,但每次当然要把所有数据都穿回去,但是是分批进行,这也是这个算法的难点与精髓所在。为了方便理解,我用迭代的方式向大家展示:最后一次(也是最接近排序完成的一次)是两个数组合并为一个,这一个temp传回给arr【】,是从temp【0】->temp【length-1】。倒数第二次:temp【0】->temp【mid】,temp【mid+1】->temp【length-1】…第一次:两两回传,(可能是)temp【0】->temp【1】,temp2->temp3依次类推。这就肯定需要循环来定位索引。
到此可以将上述方法抽象为 mergeSort()和merge()。mergeSort()多次递归调用自己而每次调用意味着分,分则要治,治则是在调用后用merge()。
从图上可以清晰的看出,整个操作是栈式操作,先分的最后再合,当然递归本身就是栈式操作,我之所以这样说是为了让大家再顺着思路分析下来能知道如何去编写这样的程序。有了这些,我们开始逐句翻译就好了。 我们知道需要一个分的总函数以及每次帮忙合的子函数,总函数递归调用自己和子函数就完成了。故此,我们开始子函数的编写,也是按照之前的思路。
实现
merge(){①比较两个数组中的每一个数,将当前索引指向的较小的数装入临时数组temp中;
merge(int arr[],int low,int mid,int high){
int i = low;
int j = mid+1;
int t = 0;
int temp[] = new int[high-low+1];
while (i<=mid && j<=high){
if (arr[i]<arr[j]){
temp[t++] = arr[i++];
}
else {
temp[t++] = arr[j++];
}
②当一组数据全部装入temp中时,一定会出现一种情况,另一组一定还有数据没存进去,所以将剩下的数存入temp;
while (i<=mid){
temp[t++] = arr[i++];
}
while (j<=high){
temp[t++] = arr[j++];
}
③ 这是比较难想到的一点:存入temp后,还需要返回到原先数组arr【】中去。但注意,虽然每次都是返回去的下标都是从0—>length-1,但不是一次性的。因为整个排序不是一次排好,每次小组内排完就需要存回arr,由此可知,不可能只用回传一次,但每次当然要把所有数据都穿回去,但是是分批进行,这也是这个算法的难点与精髓所在。为了方便理解,我用迭代的方式向大家展示:最后一次(也是最接近排序完成的一次)是两个数组合并为一个,这一个temp传回给arr【】,是从temp【0】->temp【length-1】。倒数第二次:temp【0】->temp【mid】,temp【mid+1】->temp【length-1】…第一次:两两回传,(可能是)temp【0】->temp【1】,temp2->temp3依次类推。这就肯定需要循环来定位索引。
for (int tempLeft=0;tempLeft<temp.length;tempLeft++){
arr[low+tempLeft] = temp[tempLeft];
}
mergeSort()多次递归调用自己而每次调用意味着分,分则要治,治则是在调用后用merge()。 我们都知道要一分为二二分为四,到元素为一时结束,反过来怎么写循环呢。当length=1 则(0+1)/2=0 此时low=high 反过来 low<high则可以不停分解
mergeSort(int arr[],int low,int high){
int mid = (low+high)/2;
if (low<high) {
mergeSort(arr, low, mid);
mergeSort(arr, mid+1, high);
//
merge(arr,low,mid,high);
System.out.println( Arrays.toString(arr));
}
}
分析
首先要将整个数组遍历一遍,归并排序要进行log2n次,总共的时间复杂度为O(nlog2n)
递归深度为log2n 额外的数组空间 n 总的空间复杂度为O(n+log2n)
再merge()中使用的是两两比较,不存在跳跃,所以归并排序是稳定的。
换而言之,归并排序是一种空间换时间的算法。
谢谢大家。
图侵删