前言:
- 归并排序是分治的思想,归并是将两个或两个有序的数组/链表组合成一个新的有序表。算法设计思路为:先将一个数组/链表拆成只剩一个元素的n个子表,因为这时只剩一个元素,因此也是有序的。再将子表两两归并,归并的结果是产生n/2个有序子表,再将这n/2个有序子表两两合并,合并成n/4个的有序子表…最终合并称为一个表,即为排完序的表。
- 归并的时间复杂度之所以比较低就是因为合并两个有序表的时间复杂度为O(n),以及二分将表划分为子表求排序。
- 二路归并的过程如下图:
1.数组的归并排序
思路同前言:先进行拆分,拆到每个子表只剩一个元素时,再将子表两两合并。
需要注意的点:
- 拆分成子表使用数组下标表示,因此需要start,end来表示一个子表的开始与结尾,合并时也一样,mid也可以不传入;
- 合并时因为需要就地改变数组的元素,因此需要一个辅助数组,排序过程中将元素放入辅助数组中,最后将辅助数组赋值给原数组;
- 合并两个有序子表的思路:使用三个指针:i指向子表1,j指向子表2,p指向辅助数组,当i指向的元素小于j指向的元素时,将该元素放入辅助数组temp中且i++,p++;否则将j指向的元素放入辅助数组,j++,p++;最后将没有遍历结束子表放入temp中
public class S912_sortArray {
List<Integer> result = new ArrayList<>();
public List<Integer> sortArray(int[] nums) {
divideList(nums,0,nums.length-1);
for (int i = 0; i < nums.length; i++) {
result.add(nums[i]);
}
return result;
}
/**
* 递归拆分和递归合并,一直拆分到只剩一个元素
*/
public void divideList(int[] nums , int start , int end){
if(start < end){
int mid = (start + end) / 2;
divideList(nums,start,mid);
divideList(nums,mid+1,end);
mergeList(nums,start,mid,end);
}
}
/**
* 因为需要使用下标直接修改数组中的元素,因此需要采用一个辅助数组temp来完成
*/
public void mergeList(int[] nums , int start , int mid , int end){
int[] temp = new int[nums.length];
int p = start;
int i = start;
int j = mid + 1;
while (i <= mid && j <= end){
if(nums[i] < nums[j]){
temp[p++] = nums[i++];
}else {
temp[p++] = nums[j++];
}
}
while (i <= mid){
temp[p++] = nums[i++];
}
while (j <= end){
temp[p++] = nums[j++];
}
for (int k = start; k <= end; k++) {
nums[k] = temp[k];
}
}
public static void main(String[] args) {
int[] nums = {49,38,65,97,76,13,27};
S912_sortArray ss = new S912_sortArray();
List<Integer> arr = ss.sortArray(nums);
for (int i:arr) {
System.out.println(i);
}
}
}
2.链表的归并排序
链表也可以使用归并来进行排序,思路也同前,只是细节不同。
需要注意的点:
- 链表二分使用快慢指针,慢指针head1的下一个节点为第二段子链表。
- 需将第一段和第二段链表拆分开
- 返回值为合并后的链表,注意返回值的使用
public class S148_sortList {
public ListNode sortList(ListNode head){
if(head != null && head.next != null){
ListNode head1 = head;
ListNode head2 = head.next;
while (head2 != null && head2.next != null){
head1 = head1.next;
head2 = head2.next.next;
}
//使用快慢指针二分之后,需要将前一段和后一段断开
ListNode temp = head1;
head2 = head1.next;
temp.next = null;
ListNode h1 = sortList(head);
ListNode h2 = sortList(head2);
return mergeLinkedList(h1 , h2);
}
return head;
}
public ListNode mergeLinkedList(ListNode head1 , ListNode head2){
ListNode h = new ListNode(-1);
ListNode p = h;
while (head1!= null && head2 != null){
if(head1.val < head2.val){
p.next = head1;
head1 = head1.next;
}else {
p.next = head2;
head2 = head2.next;
}
p = p.next;
}
p.next = head1 == null ? head2 : head1;
return h.next;
}
}
3.链表数组的归并排序
leetcode-23 合并k个有序链表
描述:链表数组中有k个有序链表,合并这k个有序链表为一个链表
思路:将链表数组进行拆分,拆分子数组只剩一个链表时,再将两个链表合并,最终合并成一个链表
需要注意的点:
- 两个链表合并后,由两个元素合并成一个了一个元素,但是存放是使用数组存放,与之前的数组归并排序有所不同,
- 使用interval 间隙来定位每次需要排序的两个子链表的位置,细节:i增加时是2*interval增加;i遍历结束的条件,要保证不越界;interval结束的条件
- 在合并函数里,将合并后的链表放入第一个子链表的位置上,最后返回lists[0]
class S23_mergeKList{
public ListNode mergeKLists(ListNode[] lists){
if(lists == null || lists.length == 0){
return null;
}
//间隙初值为1
int interval = 1;
while(interval < lists.length){
for(int i = 0; i < lists.length - interval; i += 2*interval){
mergeLinkedList(lists,i,i+interval);
}
interval *= 2;
}
return lists[0];
}
public void mergeLinkedList(ListNode[] lists, int h1,int h2){
ListNode head1 = lists[h1];
ListNode head2 = lists[h2];
ListNode h = new ListNode(-1);
ListNode p = h;
while (head1!= null && head2 != null){
if(head1.val < head2.val){
p.next = head1;
head1 = head1.next;
}else {
p.next = head2;
head2 = head2.next;
}
p = p.next;
}
p.next = head1 == null ? head2 : head1;
lists[h1] = h.next;
}
}