java 并归排序_JAVA并归排序

fb3ca5a72d33f08f3870c0b37a0f0205.png

并归排序与快速排序相似,靠分治思想突破了排序算法 O(n2) 的瓶颈。

我们看回顾一下几大排序算法的时间、空间复杂度:

排序算法平均时间复杂度最坏时间复杂度空间复杂度是否稳定

冒泡排序

O(n2)

O(n2)

O(1)

选择排序

O(n2)

O(n2)

O(1)

不是

直接插入排序

O(n2)

O(n2)

O(1)

归并排序

O(nlogn)

O(nlogn)

O(n)

快速排序

O(nlogn)

O(n2)

O(logn)

不是

堆排序

O(nlogn)

O(nlogn)

O(1)

不是

希尔排序

O(nlogn)

O(ns)

O(1)

不是

计数排序

O(n+k)

O(n+k)

O(n+k)

基数排序

O(N∗M)

O(N∗M)

O(M)

早期的排序算法总是免不了元素间的一一比较,因此时间复杂度很难突破 O(n2) 。而并归排序采用分治的思想将问题的规模缩小,使用小问题的解来解决大问题,并由此突破了 n2的诅咒。

以冒泡排序为例,我们需要n次遍历,每次遍历将数组中最大或者最小的元素冒到顶端,而这样的遍历需要 n-1 次。本质上每次遍历等于从所有元素中找到最大或者最小的元素,这就要求我们需要遍历和比较到数组中未排序的每一个元素。

所以冒泡排序的计算次数为 n-1 + n-2 + n-3 +...+1 = n(n+1)/2 ,时间复杂度表示为 O(n2)。

那么我们想一下,如果我们不是对一个杂乱的序列进行排序,而是对两个有序的子序列进行排序的话情况会是怎样的:

我们可以维护两个指针分别指向两个子序列的顶端,选择较小的元素放入新的序列,并向后移动指向拿走的元素的指针。这样我们从未排序的元素中选出一个最小或最大的数只要比较一次。

我们可以不断的缩小排序序列的范围来构建有序的子序列,从下向上一层一层逐步完成对整个序列的排序。

缩小的排序范围的过程是这样的,不断的将序列分解为俩个子序列,直到序列无法分解。比如一个序列长度为8:

两个长度为4的子序列--->四个长度为2的子序列---->八个长度为1的子序列。

分解过程就像一颗 B树 向下分裂(不同的是分裂时父节点不变),第 n 层的拥有 2n 个节点,也就是说直到每个节点中只包含一个元素时共分裂 log2n 次。

而每一层总的元素数不变,使该层所有序列变为有序数列需要 n 次比较。

整个过程下来,我们需要比较 nlog2n 次。也就是并归排序的时间复杂度为 O( nlog2n ) 。

(也不知道为什么,用小问题推导大问题总是比直接解决大问题来的快,可能是程序员的命吧。其实个人觉着不管什么问题,如果有办法用子问题来推导原问题,那么时间复杂度中一定包含log分解出的子问题数量问题规模,一旦觉着自己当前尝试的解法比该解法时间复杂度高,不妨尝试一下分治。)

所以我们有两个关键步骤:分解为子序列、合并子序列为一个有序序列。

下面上代码,注释比较全,以下两种解法都已在leetcode提交通过:

/*** @Author Nxy

* @Date 2019/12/4

* @Param

* @Return

* @Exception

* @Description 数组并归排序

* 将begin、end间的数组分解为两个子序列并回归排序*/

public static void mergeSort(int[] nums, int begin, intend) {int length =nums.length;//回归条件,子序列长度为一时返回

if (begin ==end) {return;

}//序列中点

int mid = (begin + end) / 2;//排序左边子序列

mergeSort(nums, begin, mid);//排序右边子序列

mergeSort(nums, mid + 1, end);//并归已排序的左右子序列

merge(nums, begin, mid, end);

}/*** @Author Nxy

* @Date 2019/12/4

* @Param

* @Return

* @Exception

* @Description 并归 begin--mid 与 mid+1--end 两个子序列*/

public static void merge(int[] nums, int begin, int mid, intend) {//临时数组大小

int length = end - begin + 1;int[] temp = new int[length];//临时数组将要填充的位置指针

int i = 0;//左子序列将要拿出的位置指针

int left =begin;//右子序列将要拿出的位置指针

int right = mid + 1;while (i

if (left == mid + 1) {

System.arraycopy(nums, right, temp, i, end- right + 1);break;

}if (right == end + 1) {

System.arraycopy(nums, left, temp, i, mid- left + 1);break;

}//选择较小的元素放入临时数组

if (nums[left] >=nums[right]) {

temp[i]=nums[right];

right++;

i++;

}else{

temp[i]=nums[left];

left++;

i++;

}

}

System.arraycopy(temp,0, nums, begin, length);//手动为临时数组去掉引用,方便连续的内存空间被及时回收

temp=null;

}

链表的并归排序与数组一个思路:

/*** @Author Nxy

* @Date 2019/12/4

* @Param

* @Return

* @Exception

* @Description 链表并归排序

* 递归分解序列为两个子序列,并向上并归排序,返回排序后的总链表

* 使用快慢指针法,快指针到终点时慢指针指向中点*/

public staticListNode mergeSort(ListNode head) {//回归条件

if (head.getNext() == null) {returnhead;

}//快指针,考虑到链表为2时的情况,fast比slow早一格

ListNode fast =head.getNext();//慢指针

ListNode slow =head;//快慢指针开跑

while (fast != null && fast.getNext() != null) {

fast=fast.getNext().getNext();

slow=slow.getNext();

}//找到右子链表头元素,复用fast引用

fast =slow.getNext();//将中点后续置空,切割为两个子链表

slow.setNext(null);//递归分解左子链表,得到新链表起点

head =mergeSort(head);//递归分解右子链表,得到新链表起点

fast =mergeSort(fast);//System.out.println(head.getValue()+" "+fast.getValue());//并归两个子链表

ListNode newHead =merge(head, fast);//ListNode.print(newHead);

returnnewHead;

}/*** @Author Nxy

* @Date 2019/12/4 14:48

* @Param

* @Return

* @Exception

* @Description 以left节点为起点的左子序列 及 以right为起点的右子序列 并归为一个有序序列并返回头元素;

* 传入的 left 及 right 都不可为 null*/

public staticListNode merge(ListNode left, ListNode right) {//维护临时序列的头元素

ListNode head;if (left.getValue() <=right.getValue()) {

head=left;

left=left.getNext();

}else{

head=right;

right=right.getNext();

}//两个子链表均存在剩余元素

ListNode temp =head;while (left != null && right != null) {//将较小的元素加入临时序列

if (left.getValue() <=right.getValue()) {

temp.setNext(left);

left=left.getNext();

temp=temp.getNext();

}else{

temp.setNext(right);

right=right.getNext();

temp=temp.getNext();

}

}//左子序列用完将右子序列余下元素加入临时序列

if (left == null) {

temp.setNext(right);

}//右子序列用完将左子序列余下元素加入临时序列

if (right == null) {

temp.setNext(left);

}

ListNode.print(head);returnhead;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值