冒泡排序作为最基础的排序方法为人所熟知,其从思路到实现都很简单,与此同时其弊端也非常明显,那就是效率很低,要一轮一轮从头到尾每个都进行比较,在时间耗费上需要的时间复杂度为O(n)级。而下面这种归并排序就要比冒泡排序效率要高出很多了。
1.归并排序基本原理
归并算法是一种采用分治法思想的一种经典应用场景,其原理是将无序数组先分为最小的子数组(每个中含有两个数字),将所有子数组进行排序,随后使子数组两两进行有序合并(以升序举例,在子数组排序后,用1数组中的第一个数与2数组中的第一个数进行比较,假如1数组第一个数较小,则将其放进新数组的第一位,用1数组的第二个数与2数组的第一个数进行比较,以此类推),逐步进行,最终将各子数组逐渐合并为一个有序数组,以此达到排序的目的。
将每次比较看为耗时为c的事件,那么第一层时间代价为cn,第二层时间代价为cn/2+cn/2=cn…每一层代价都是cn,总共有logn+1层。所以总的时间代价为cn*(logn+1).时间复杂度是o(nlogn).
归并的空间复杂度就是那个临时的数组和递归时压入栈的数据占用的空间:n + logn;所以空间复杂度为: O(n)
归并排序比较稳定,在时间上是非常有效的(最差时间复杂度和最优时间复杂度都为 O(nlogn) ),但是这种算法很消耗空间,
2.代码实现
在归并排序的代码实现中总共分为两部分
- 将原数组拆成各个子数组,每个子数组里面只有一个数,只有一个数的数组就是有序数组了
- 合并有序的子数组,一直到合并到一个为止
首先,我们需要将已知数组分为一个一个只有一个数的数组,但是由于只有要进行数组合并所以要采用二维数组
public static int[] merge( int[] arr ) {
int[][] arry = new int[arr.length][1];
/**
* 拆成子数组,每个子数组里面只有一个数,所以只有一个数的数组就是有序数组
*/
for(int i = 0; i < arr.length; i++){
arry[i] = new int[] { arr[i] };
}
然后进行排序
/**
* 合并有序的子数组,一直到合并到一个为止
*/
int[][] arr3 = arry;
for(;;) {
if( arr3.length > 1 ) {
arr3 = SortClass.getHalf(arr3);
}else {
break;
}
}
return arr3[0];
}
借助编写的getHalf函数将子数组减少到原来的一半
/**
* 压缩自身数组元素至原来的一半
* @return
*/
public static int[][] getHalf(int[][] arr) {
int[][] result;
if( arr.length % 2 == 1 ) {
result = new int[ arr.length / 2 + 1][];
}else {
result = new int[ arr.length / 2][];
}
for( int i = 0; i < result.length; i++) {
if( (i * 2 + 1) >= arr.length ) {
result[i] = mergeArr(arr[i*2],new int[] {});
}else {
result[i] = mergeArr(arr[i*2],arr[i*2 + 1]);
}
}
return result;
}
}
调用有序合并两个数组的mergeArr方法
/**
* 入参是两个有序的由小到大的数组
* @param arr1
* @param arr2
* @return 返回由小到大的有序数组
*/
public static int[] mergeArr(int[] arr1, int[] arr2) {
int[] result = new int[ arr1.length + arr2.length ];
int startArr1 = 0;
int startArr2 = 0;
int insertIndex = 0;
for(;;) {
if( startArr1 > arr1.length - 1 || startArr2 > arr2.length - 1 ) {
break;
}
if( arr1[startArr1] <= arr2[startArr2] ) {
result[insertIndex] = arr1[startArr1];
startArr1 += 1;
}else {
result[insertIndex] = arr2[startArr2];
startArr2 += 1;
}
insertIndex += 1;
}
if( startArr1 > arr1.length - 1 ) {
for( int k = startArr2; k < arr2.length; k++ ) {
result[insertIndex] = arr2[k];
// startArr2 += 1;
insertIndex += 1;
}
}else {
for( int k = startArr1; k < arr1.length; k++ ) {
result[insertIndex] = arr1[startArr1];
startArr1 += 1;
insertIndex += 1;
}
}
return result;
}
总体来说由主函数merge(int[])函数进行数组拆分,在主函数merge(int[])中调用getHalf(int[][])函数将子数组数量将至原来的一半,其中在getHalf(int[][])函数中调用mergeArr(int[], int[])函数实现对两个数组的有序合并。
3.完整代码
public static int[] merge( int[] arr ) {
int[][] arry = new int[arr.length][1];
/**
* 拆成子数组,每个子数组里面只有一个数,所以只有一个数的数组就是有序数组
*/
for(int i = 0; i < arr.length; i++){
arry[i] = new int[] { arr[i] };
}
/**
* 合并有序的子数组,一直到合并到一个为止
*/
int[][] arr3 = arry;
for(;;) {
if( arr3.length > 1 ) {
arr3 = SortClass.getHalf(arr3);
}else {
break;
}
}
return arr3[0];
}
/**
* 入参是两个有序的由小到大的数组
* @param arr1
* @param arr2
* @return 返回由小到大的有序数组
*/
public static int[] mergeArr(int[] arr1, int[] arr2) {
int[] result = new int[ arr1.length + arr2.length ];
int startArr1 = 0;
int startArr2 = 0;
int insertIndex = 0;
for(;;) {
if( startArr1 > arr1.length - 1 || startArr2 > arr2.length - 1 ) {
break;
}
if( arr1[startArr1] <= arr2[startArr2] ) {
result[insertIndex] = arr1[startArr1];
startArr1 += 1;
}else {
result[insertIndex] = arr2[startArr2];
startArr2 += 1;
}
insertIndex += 1;
}
if( startArr1 > arr1.length - 1 ) {
for( int k = startArr2; k < arr2.length; k++ ) {
result[insertIndex] = arr2[k];
// startArr2 += 1;
insertIndex += 1;
}
}else {
for( int k = startArr1; k < arr1.length; k++ ) {
result[insertIndex] = arr1[startArr1];
startArr1 += 1;
insertIndex += 1;
}
}
return result;
}
/**
* 压缩自身数组元素至原来的一半
* @return
*/
public static int[][] getHalf(int[][] arr) {
int[][] result;
if( arr.length % 2 == 1 ) {
result = new int[ arr.length / 2 + 1][];
}else {
result = new int[ arr.length / 2][];
}
for( int i = 0; i < result.length; i++) {
if( (i * 2 + 1) >= arr.length ) {
result[i] = mergeArr(arr[i*2],new int[] {});
}else {
result[i] = mergeArr(arr[i*2],arr[i*2 + 1]);
}
}
return result;
}
}