归并排序是一种基于分而治之的排序技术。最坏情况下的时间复杂度为O(nlogn),它是最受人尊敬的算法之一。归并排序首先将数组分成相等的两半,然后以排序的方式将它们合并。
为了理解合并排序,我们采用未排序的数组,如下所示
我们知道归并排序首先将整个数组迭代地分成相等的一半,除非获得原子值。我们在这里看到一个由8个项目组成的数组分为两个大小为4的数组。
这不会更改原件中项目出现的顺序。现在我们将这两个数组分为两半。
我们进一步划分这些数组,并获得无法再划分的原子值
现在,我们将它们分解时的方式完全相同。请注意提供给这些列表的颜色代码。
我们首先比较每个列表的元素,然后以排序的方式将它们组合到另一个列表中。我们看到14和33处于排序位置。我们比较27和10,在2个值的目标列表中,我们先放置10,然后是27。我们更改19和35的顺序,而将42和44顺序放置。
在合并阶段的下一个迭代中,我们比较两个数据值的列表,然后将它们合并为找到的数据值的列表,将所有数据按排序顺序放置。
最终合并后,列表应如下所示:
归并排序会继续将列表分为相等的一半,直到无法再对其进行划分为止。根据定义,如果它只是列表中的一个元素,则会对其进行排序。然后,合并排序将合并较小的排序列表,同时也将新列表排序。
Step 1−将列表递归分为两半,直到无法再将其划分为止。
Step 2−将若干个组两两合并,保证合并后的序列有序。
step 3-重复第二步操作直到只剩下一组,排序完成。
package lipan.top.notes.basicsort.impl;
import lipan.top.notes.basicsort.ISort;
import java.util.Arrays;
/**
*
* title: 基础排序-归并排序
* @author: li.pan
* Date: 2019/12/7 12:15 下午
* Version: V1.0
* Description:
*/
public class MergeSort implements ISort {
@Override
public void sort(Integer[] arr) {
int n = arr.length;
sort(arr, 0, n - 1);
}
//递归使用归并排序,对arr[l....r]的范围进行进行排序 0,10 0/5 0/2
private static void sort(Integer[] arr, int l, int r) {
System.out.println("sort: arr=" + Arrays.toString(arr) + ",l=" + l + ",r=" + r);
if (l >= r) { //当子序列中只有一个元素递归到底的情况
System.out.println("------------------------------------------------------------------------------------------------");
return;
}
int mid = (l + r) / 2; // l=0 r=2
sort(arr, l, mid);
sort(arr, mid + 1, r);
merge(arr, l, mid, r);
}
//将arr[l...mid]和arr[mid+1...r]两部分进行归并
private static void merge(Integer[] arr, int l, int mid, int r) {
System.out.println("merge: arr=" + Arrays.toString(arr) + ",l=" + l + ",mid=" + mid + ",r=" + r);
// 开辟临时空间,合并左半部分已经排好序的数组和右半部分已经排好序的数组
Integer[] aux = Arrays.copyOfRange(arr, l, r + 1);
System.out.println("merge: arr=" + Arrays.toString(aux) + ",l=" + l + ",mid=" + mid + ",r=" + r);
// 初始化,i指向左半部分的起始位置索引;j指向右半部分起始索引位置mid+1
int i = l, j = mid + 1;
for (int k = l; k <= r; k++) { // k指向两个元素比较后归并下一个需要放置的位置
System.out.println("第K=" + k + "趟");
/**
* 考虑数组越界
*/
if (i > mid) { // 如果左半部分元素已经全部处理完毕,
arr[k] = aux[j - l];
j++;
} else if (j > r) { // 如果右半部分元素已经全部处理完毕
arr[k] = aux[i - l];
i++; // l表示偏移
}
/**
* 真正比较: 两两比较放入新的序列arr
*/
else if (aux[i - l] < aux[j - l]) { // 左半部分所指元素 < 右半部分所指元素
arr[k] = aux[i - l];
i++;
} else { // 左半部分所指元素 >= 右半部分所指元素(右边的放置待排序前面)
arr[k] = aux[j - l];
j++;
}
}
System.out.println("\n");
}
}
------------------------------随机数组--------------------------------
插入排序测试1数据量为100排序时长为: 0.001s
插入排序测试2数据量为10000排序时长为: 0.084s
插入排序测试3数据量为100000排序时长为: 5.868s
冒泡排序测试1数据量为100排序时长为: 0.0s
冒泡排序测试2数据量为10000排序时长为: 0.061s
冒泡排序测试3数据量为100000排序时长为: 9.069s
希尔排序测试1数据量为100排序时长为: 0.0s
希尔排序测试2数据量为10000排序时长为: 0.004s
希尔排序测试3数据量为100000排序时长为: 0.008s
虽然归并排序是nlogn级别的算法, 但是在数组数据量比较小的时候, 插入排序的效率仍然是高于归并排序的, 所以可以在对数组分解到足够小之后, 使用插入排序, 然后再递归进行归并排序。
如果一个数组是近乎有序的, 或者说是完全有序的, 上述步骤会有很多无用的merge操作, 所以可以在进行merge前增加一个判断, 效率也会有一定的提高。
package lipan.top.notes.basicsort.impl.optimize;
import lipan.top.notes.basicsort.ISort;
import java.util.Arrays;
/**
*
* title: 归并排序优化
* @author: li.pan
* Date: 2019/12/7 12:15 下午
* Version: V1.0
* Description:
* 优化1: 对于arr[mid] <= arr[mid+1]的情况,不进行merge
* 优化2: 对于小规模数组, 使用插入排序
*
*/
public class MergeSortOptimize implements ISort {
@Override
public void sort(Integer[] arr) {
int n = arr.length;
sort(arr, 0, n - 1);
}
//递归使用归并排序,对arr[l....r]的范围进行进行排序
private static void sort(Integer[] arr, int l, int r) {
/**
* 优化2: 对于小规模数组, 使用插入排序
*/
if (r - l <= 15) {
insertionSort(arr, l, r);
return;
}
int mid = (l + r) / 2;
sort(arr, l, mid);
sort(arr, mid + 1, r);
/**
* 优化1: 对于arr[mid] <= arr[mid+1]的情况,不进行merge
* 对于近乎有序的数组非常有效,但是对于一般情况,有一定的性能损失
*/
if (arr[mid] > arr[mid + 1])
merge(arr, l, mid, r);
}
//将arr[l...mid]和arr[mid+1...r]两部分进行归并
private static void merge(Integer[] arr, int l, int mid, int r) {
// 开辟临时空间,合并左半部分已经排好序的数组和右半部分已经排好序的数组
Integer[] aux = Arrays.copyOfRange(arr, l, r + 1);
// 初始化,i指向左半部分的起始位置索引;j指向右半部分起始索引位置mid+1
int i = l, j = mid + 1;
for (int k = l; k <= r; k++) { // k指向两个元素比较后归并下一个需要放置的位置
/**
* 考虑数组越界
*/
if (i > mid) { // 如果左半部分元素已经全部处理完毕,
arr[k] = aux[j - l];
j++;
} else if (j > r) { // 如果右半部分元素已经全部处理完毕
arr[k] = aux[i - l];
i++; // l表示偏移
}
/**
* 真正比较
*/
else if (aux[i - l] < aux[j - l]) { // 左半部分所指元素 < 右半部分所指元素
arr[k] = aux[i - l];
i++;
} else { // 左半部分所指元素 >= 右半部分所指元素
arr[k] = aux[j - l];
j++;
}
}
}
// 对arr[l...r]的区间使用InsertionSort排序
public static void insertionSort(Integer[] arr, int l, int r) {
for (int i = l + 1; i <= r; i++) {
Integer e = arr[i];
int j = i;
for (; j > l && arr[j - 1] > e; j--)
arr[j] = arr[j - 1];
arr[j] = e;
}
}
}
------------------------------随机数组--------------------------------
插入排序测试1数据量为100排序时长为: 0.001s
插入排序测试2数据量为10000排序时长为: 0.12s
插入排序测试3数据量为100000排序时长为: 8.578s
归并排序测试1数据量为100排序时长为: 0.0s
归并排序测试2数据量为10000排序时长为: 0.013s
归并排序测试3数据量为100000排序时长为: 0.025s
归并排序优化测试1数据量为100排序时长为: 0.0s
归并排序优化测试2数据量为10000排序时长为: 0.001s
归并排序优化测试3数据量为100000排序时长为: 0.003s
------------------------------近乎有序数组--------------------------------
插入排序测试1数据量为100排序时长为: 0.0s
插入排序测试2数据量为10000排序时长为: 0.039s
插入排序测试3数据量为100000排序时长为: 2.764s
归并排序测试1数据量为100排序时长为: 0.0s
归并排序测试2数据量为10000排序时长为: 0.0s
归并排序测试3数据量为100000排序时长为: 0.009s
归并排序优化测试1数据量为100排序时长为: 0.0s
归并排序优化测试2数据量为10000排序时长为: 0.0s
归并排序优化测试3数据量为100000排序时长为: 0.0s