一.归并排序
归并排序用到了分治思想,是将一个大问题分解成小的子问题来解决。小问题解决了,则大问题也就解决了。归并排序的核心思想:如果要排序一个数组,我们先把数组从中间分成前后两部分,然后对前后两部分分别排序,再将排好的两部分合并在一起,这样整个数组就都有序了。
参考文章
二,归并排序算法详细分析
归并排序算法有两个基本的操作,一个是分,也就是把原数组划分成两个子数组的过程。另一个是治,它将两个有序数组合并成一个更大的有序数组。
它将数组平均分成两部分: mid= (left + right)/2,当数组分得足够小时—数组中只有一个元素时,只有一个元素的数组自然而然地就可以视为是有序的,此时就可以进行合并操作了。因此,上面讲的合并两个有序的子数组,是从 只有一个元素 的两个子数组开始合并的。
比如初始数组:[24,13,26,1,2,27,38,15]
①分成了两个大小相等的子数组:[24,13,26,1] [2,27,38,15]
②再划分成了四个大小相等的子数组:[24,13] [26,1] [2,27] [38,15]
③此时,left < right 还是成立,再分:[24] [13] [26] [1] [2] [27] [38] [15]
此时,有8个小数组,每个数组都可以视为有序的数组了!!!,每个数组中的left == right,
merge([24],[13]) 得到 [13,24]
merge([26],[1]) 得到[1,26]
…
…
最终得到 有序数组。
代码实现1:
package data.Sort;
public class MergerSort {
public static void main(String[] args) {
int[] data = new int[]{1,41,14,5,8,5,6,7,11,9};
int n=data.length;
mergerSort (data,0,n-1);
arrPrint(data);
}
public static void mergerSort(int[] data,int left,int right) { //使用递归方法把数组分解成若干子数组
if(left>=right){
return;
}
else{
int mid=(left+right)/2;
mergerSort(data,left,mid);
mergerSort(data,mid+1,right);
merger(data,left,mid,right);
}
}
public static void merger(int[] data,int left,int mid,int right){ //合并若干子数组
int i=left;
int j=mid+1;
int k=0;
int[] temp=new int[right-left+1];
// 两部分数组都还有数据
while(i<=mid && j<=right){
// 小于等于保证有效性
if(data[i]<=data[j]){
temp[k++]=data[i++];
}else{
temp[k++]=data[j++];
}
}
// 判断两个数组中哪个还有元素
int start=i;
int end=mid;
//胜下第二个数组
if(j<=right){
start=j;
end=right;
}
//将剩余数组拷贝回temp数组
while(start<=end){
temp[k++]=data[start++];
}
// 将temp中数组拷贝回data[left,right]数组
for(i=0;i<=right-left;i++){
data[left+i]=temp[i];
}
}
public static void arrPrint(int[] data){
for(int i=0;i<data.length;i++){
System.out.print(data[i]+" ");
}
}
}
代码实现2:
package data.Sort;
public class MergerSort1 {
public static int[] mergeSort(int[] nums, int left, int right) {
if (left==right)
return new int[] { nums[left] };
int mid = left+ (right - left) / 2;
int[] leftArr = mergeSort(nums, left, mid); //左有序数组
int[] rightArr = mergeSort(nums, mid + 1, right); //右有序数组
int[] newNum = new int[leftArr.length + rightArr.length]; //新有序数组
int m = 0, i = 0, j = 0;
while (i < leftArr.length && j < rightArr.length) {
newNum[m++] = leftArr[i] < rightArr[j] ? leftArr[i++] : rightArr[j++];
}
while (i < leftArr.length)
newNum[m++] = leftArr[i++];
while (j < rightArr.length)
newNum[m++] = rightArr[j++];
return newNum;
}
public static void main(String[] args) {
int[] nums = new int[] { 9, 8, 7, 6, 5, 4, 3, 2, 10 };
int[] newNums = mergeSort(nums, 0, nums.length - 1);
for (int x : newNums) {
System.out.print(x+",");
}
}
}
四,归并排序算法复杂度分析
-
归并排序中,用到了一个临时数组,故空间复杂度为O(N)
-
由归并排序的递归公式:T(N) = 2T(N/2) + O(N) 可知时间复杂度为O(NlogN),数组的初始顺序会影响到排序过程中的比较次数,但是总的而言,对复杂度没有影响。平均情况 or 最坏情况下 它的复杂度都是O(NlogN)
-
归并排序是一个稳定的排序算法
-
此外,归并排序中的比较次数是所有排序中最少的。原因是,它一开始是不断地划分,比较只发生在合并各个有序的子数组时。因此,JAVA的泛型排序类库中实现的就是归并排序。因为:对于JAVA而言,比较两个对象的操作代价是很大的(根据Comparable接口的compareTo方法进行比较),而移动两个对象,其实质移动的是引用,代价比较小。(排序本质上是两种操作:比较操作和移动操作)
java.util.Arrays.sort(T[] arr)使用的是归并排序
java.util.Arrays.sort(int[] arr) 使用的是快速排序