归并排序是一种类似于分治的思想。
把整个数组不停地对半分割,然后进行递归。
递归一直持续到每个分组只剩一个元素,就结束递归,进行排序。
排序就是合并两个有序数组。
从递归深层一直往外层合并数组,到最后就完全合并成一个有序数组。
具体看下面代码的注解吧!
// 归并排序(Java-递归版)
static void merge_sort_recursive(int[] arr, int[] result, int start, int end) {
if (start >= end)//递归结束条件
return;
int len = end - start, mid = (len >> 1) + start;//分割数组
int start1 = start, end1 = mid;
int start2 = mid + 1, end2 = end;
merge_sort_recursive(arr, result, start1, end1);//数组左边递归
merge_sort_recursive(arr, result, start2, end2);//数组右边递归
int k = start;
while (start1 <= end1 && start2 <= end2)//合并数组
result[k++] = arr[start1] < arr[start2] ? arr[start1++] : arr[start2++];
while (start1 <= end1)
result[k++] = arr[start1++];
while (start2 <= end2)
result[k++] = arr[start2++];
for (k = start; k <= end; k++)//将合并之后的数据更新到原数组中
arr[k] = result[k];
}
public static void merge_sort(int[] arr) {
int len = arr.length;
int[] result = new int[len];
merge_sort_recursive(arr, result, 0, len - 1);
}
时间复杂度分析
归并排序内部是一直对半分,然后递归,直到分到每组只有一个元素就开始合并。
因此递归的总层数是log₂n。
合并从最底层开始,合并是把两个有序数组合并成一个有序数组。因此每次都会把所以元素都遍历一次,复杂度为n。
因此总的时间复杂度就是O(n*log₂n)。
无论如何算法都会进行上述操作,因此归并排序算法是稳定的。
顺便说一下快排的复杂度
快排的复杂度在nlog₂n到n²之间。
最好时间复杂度就是快排每次获取的标志位都刚好将数组平均分成两半,这样就和归并排序差不多,一直分到每边只有一个元素,复杂度为log₂n。
在进行元素交换的时候,每一层递归都会把整个数组都遍历一遍,因此复杂度为n。
总的复杂度就是nlog₂n。
最差的时间复杂度就是在进行分割的时候每次全部元素都在一边,这样分割的复杂度就是n。
元素交换的复杂度为n。
总的复杂度就是n²。
利用归并排序排序链表
原理和数组排序差不多,不过有几个点不一样.
首先,在排序数组的时候,下一层递归的结果是通过将值赋到数组里面,这样上一层可以知道合并之后的数组.
而链表只能返回合并之后的头节点给上一层,让上一层可以使用下一层递归的结果.
合并函数需要的也是两个链表头,因此两个函数外层用合并链表函数,参数递归归并函数.
看看源码
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode sortList(ListNode head) {
if(head==null||head.next==null){
return head;
}
return merge(head);
}
private ListNode merge(ListNode head){
//递归分割,然后合并,因为合并之后的链表需要知道头节点上一层才能用,因此返回头节点
if(head.next==null){
//最后只剩一个节点的时候就返回该节点,该节点也就是一个节点时的头节点
return head;
}
ListNode mid=findMid(head);
ListNode right_start=mid.next;
mid.next=null;//在左递归的链表在尾部截断,
return mergeTwoList(merge(head),merge(right_start));
//参数里面的递归返回递归函数合并链表之后的头节点
}
private ListNode findMid(ListNode head){
//查找中间节点
if(head==null){
return null;
}
ListNode fast=head;
ListNode low=head;
while(fast.next!=null&&fast.next.next!=null){
fast=fast.next.next;
low=low.next;
}
return low;
}
private ListNode mergeTwoList(ListNode a,ListNode b){
//返回合并之后链表的头节点
ListNode newHead=new ListNode();
ListNode temp=newHead;
while(a!=null&&b!=null){
if(a.val>b.val){
temp.next=b;
b=b.next;
}else{
temp.next=a;
a=a.next;
}
temp=temp.next;
}
if(a!=null){
//将剩下的链表直接接在链表尾部
temp.next=a;
}else{
temp.next=b;
}
return newHead.next;
}
}