归并:将两个有序数组合并成一个有序数组
public void merge(int left,int right,int mid,int[] num)
//标记左半区第一个未排序的元素
int l_pos=left;
//标记右半区第一个未排序的元素
int r_pos=mid+1;
int pos=left;
while(l_pos<=mid&&r_pos<=right){
if(nums[l_pos]<nums[r_pos])
temp[pos++]=nums[l_pos++];
else
temp[pos++]=nums[r_pos++];
}
//合并左半区剩余的元素,剩余元素已经有序
while(l_pos<=mid)
temp[pos++]=nums[l_pos++];
//合并右半区剩余的元素,剩余元素已经有序
while (r_pos<=right)
temp[pos++]=nums[r_pos++];
//把临时数组中合并后的元素复制回原来的数组
int j=0;
for(int i=left;i<=right;i++)
nums[i]=temp[i];
}
对于一个完全无序的数组,使用归并排序,要用到分治和递归的思想
分治
归并排序
public static void main(String[] args) {
int[] nums={6,3,8,0,1,3,2,3,3,3,8,4,10,12,3};
//归并排序需要一个额外的空间
int[] temp=new int[nums.length];
new Merge().mergeSort(0,nums.length-1,nums,temp);
for(int n:nums)
System.out.print(n);
}
public void mergeSort(int left,int right,int[] nums,int[] temp){
//如果只有一个元素,那么不需要继续划分,只有一个元素的区域
//本身就是有序的,只需要被归并即可
if(left==right)
return;
else {
int mid = left + (right - left) / 2;
//[left,mid] [mid+1,right]
//递归划分左半区,进行排序
mergeSort(left, mid, nums,temp);
//递归划分右半区,进行排序
mergeSort(mid+1, right, nums,temp);
//合并已经排序的部分
merge(left, right, mid,nums,temp);
}
}
链表的归并排序
递归的空间复杂度由递归的深度决定,归并排序递归深度为logn(二分法决定),空间复杂度为O(logn)
递归
/* 知识点1:归并排序的整体思想
* 知识点2:找到一个链表的中间节点的方法
* 知识点3:合并两个已排好序的链表为一个新的有序链表
*/
class Solution {
public ListNode sortList(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 rightHead = slow.next; //链表第二部分的头节点
slow.next = null; //cut 链表
ListNode left = sortList(head); //递归排序前一段链表
ListNode right = sortList(rightHead); //递归排序后一段链表
return merge(left,right);
}
public ListNode merge(ListNode h1,ListNode h2){ //合并两个有序链表
ListNode dummy = new ListNode(-1);
ListNode p = dummy;
while(h1!=null && h2!=null){
if(h1.val < h2.val){
p.next = h1;
h1 = h1.next;
}else{
p.next = h2;
h2 = h2.next;
}
p = p.next;
}
if(h1!=null) p.next = h1;
else if(h2!=null) p.next = h2;
return dummy.next;
}
}
迭代:使用迭代替代分割环节,分割环节本质上是通过二分法得到链表最小节点单元,再通过多轮合并得到排序结果(从底至顶直接合并)
递归主要实现分割,那么需要使用迭代替代递归直接实现分割环节,从前到后
class Solution {
public ListNode sortList(ListNode head) {
int length = getLength(head);
ListNode dummy = new ListNode(-1);
dummy.next = head;
for(int step = 1; step < length; step*=2){ //依次将链表分成1块,2块,4块...
//每次变换步长,pre指针和cur指针都初始化在链表头
ListNode pre = dummy;
ListNode cur = dummy.next;
while(cur!=null){
ListNode h1 = cur; //第一部分头 (第二次循环之后,cur为剩余部分头,不断往后把链表按照步长step分成一块一块...)
ListNode h2 = split(h1,step); //第二部分头
cur = split(h2,step); //剩余部分的头
ListNode temp = merge(h1,h2); //将一二部分排序合并
pre.next = temp; //将前面的部分与排序好的部分连接
while(pre.next!=null){
pre = pre.next; //把pre指针移动到排序好的部分的末尾
}
}
}
return dummy.next;
}
public int getLength(ListNode head){
//获取链表长度
int count = 0;
while(head!=null){
count++;
head=head.next;
}
return count;
}
public ListNode split(ListNode head,int step){
//断链操作 返回第二部分链表头
if(head==null) return null;
ListNode cur = head;
for(int i=1; i<step && cur.next!=null; i++){
cur = cur.next;
}
ListNode right = cur.next;
cur.next = null; //切断连接
return right;
}
public ListNode merge(ListNode h1, ListNode h2){
//合并两个有序链表
ListNode head = new ListNode(-1);
ListNode p = head;
while(h1!=null && h2!=null){
if(h1.val < h2.val){
p.next = h1;
h1 = h1.next;
}
else{
p.next = h2;
h2 = h2.next;
}
p = p.next;
}
if(h1!=null) p.next = h1;
if(h2!=null) p.next = h2;
return head.next;
}
}