文章目录
- 一、分治法思想
- 二、归并排序的效率
- 二、Java代码实现
- 1.数组形式
- 2.链表形式
- 三、改进
- 1.数据量小
- 2. 左右数组已经完全排好序
归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
一、分治法思想
二、归并排序的效率
- 时间复杂度O(nlogn)
- 空间复杂度O(n)
二、Java代码实现
1.数组形式
第一种归并排序被称为自顶向下的归并排序,这也是最经典的版本,它基于分治法,即将大数组拆分为左右两个小数组【小数组再次拆分为左右两个小数组直到不可拆分】,然后调用归并方法将已经拆分好并且有序的小数组归并成大数组即完成了归并排序。
public void sort(int[] array) {
int[] aux = new int[array.length];
int left = 0;
int right = array.length - 1;
mergeSort(array, aux, left, right);
}
private void mergeSort(int[] array, int[] aux, int left, int right) {
// 递归终止条件
if (left >= right) {
return;
}
// 划分左右数组:分
int mid = left + ((right - left)>>1);
mergeSort(array, aux, left, mid);
mergeSort(array, aux, mid + 1, right);
// 合并当前的两个左右数组:治
merge(array, aux, left, mid, right);
}
private void merge(int[] array, int[] aux, int left, int mid, int right) {
// 左链表最右边索引
int i = mid;
// 右链表最右边索引
int j = right;
// 辅助链表最右边索引
int cur = right;
while (i >= left && j > mid) {
if (array[i] >= array[j]) {
aux[cur--] = array[i--];
} else {
aux[cur--] = array[j--];
}
}
// 将剩余的数据复制到辅助数组中
while (i >= left) {
aux[cur--] = array[i--];
}
while (j > mid) {
aux[cur--] = array[j--];
}
// 将辅助数组中已排序部分复制到原数组
for (int k = left; k <= right; k++) {
array[k] = aux[k];
}
}
2.链表形式
public ListNode sort(ListNode head) {
//递归终止条件
if (head == null || head.next == null){
return head;
}
// 使用快慢指针将链表拆分成左右两个链表
ListNode slow = head;
ListNode fast = head.next;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
ListNode rightStart = slow.next;
slow.next = null;
//分割链表成两个部分:分
ListNode l1 = sort(head);
ListNode l2 = sort(rightStart);
//合并两个链表:治
ListNode res = merge(l1, l2);
return res;
}
private ListNode merge(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(-1);
ListNode cur = dummy;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
cur.next = l1;
l1 = l1.next;
} else {
cur.next = l2;
l2 = l2.next;
}
cur = cur.next;
}
if (l1 != null) {
cur.next = l1;
}
if (l2 != null) {
cur.next = l2;
}
return dummy.next;
}
三、改进
归并排序和快锁排序一样依赖于递归和分治,这在大数组的情况下性能优异,归并排序的时间复杂度为0(nlogn),但是当数组比较小的时候,归并排序由于大量的递归调用以及需要创建空间复杂度为0(n)的辅助空间,性能或许比插入排序或者选择排序低。所以这时可以有两种选择:
1.数据量小
当数据量小到一定范围时可以选择插入排序:
public static void sort(int[] a, int[] aux, int left, int right) {
//数据范围,视情况而定
int N = 100;
if (right - left <= N) {
//改用插入排序
InsertSort(a);
return;
}
//中间划分点
int middle = left + (right - left)/2;
//将左半边排序
sort(a, aux, left, middle);
//将右半边排序
sort(a, aux, middle + 1, right);
//归并
merge(a, aux, left, middle, right);
}
2. 左右数组已经完全排好序
检测待归并的两个子数组是否已经有序,如果已经有序则直接复制进aux中。