入秋的第一篇数据结构算法:看看归并与快排的风采

  • @ClassName Print

  • @Description 打印

  • @date 2020/9/17 11:13

*/

public class Print {

/***

  • 打印数据

  • @param arr 数组

  • @param length 数组长度

*/

public static void print(int[] arr, int length) {

for (int i = 0; i < length; ++i) {

System.out.print(arr[i] + " ");

}

System.out.println(“”);

}

}

排序前数组===========

6 4 1 7 2 5 8 3

合并后的数据

4 6 1 7 2 5 8 3

合并后的数据

4 6 1 7 2 5 8 3

合并后的数据

1 4 6 7 2 5 8 3

合并后的数据

1 4 6 7 2 5 8 3

合并后的数据

1 4 6 7 2 5 3 8

合并后的数据

1 4 6 7 2 3 5 8

合并后的数据

1 2 3 4 5 6 7 8

排序后数组===========

1 2 3 4 5 6 7 8

Process finished with exit code 0


3.时间空间复杂度分析


我们假设对 n 个元素进行归并排序需要的时间是 T(n),那分解成两个子数组排序的时间都是 T(n/2)。我们知道,merge() 函数合并两个有序子数组的时间复杂度是 O(n)。所以,套用前面的公式,归并排序的时间复杂度的计算公式就是:

T(1) = C; n=1时,只需要常量级的执行时间,所以表示为C。

T(n) = 2*T(n/2) + n; n>1

通过这个公式,如何来求解 T(n) 呢?还不够直观?那我们再进一步分解一下计算过程。

T(n) = 2*T(n/2) + n

= 2*(2T(n/4) + n/2) + n = 4T(n/4) + 2*n

= 4*(2T(n/8) + n/4) + 2n = 8T(n/8) + 3n

= 8*(2T(n/16) + n/8) + 3n= 16T(n/16) + 4n

= 2^k * T(n/2^k) + k * n

通过这样一步一步分解推导,我们可以得到 T(n) = 2 k 2^k 2k T(n/ 2 k 2^k 2k)+kn。当 T( n / 2 k n/2^k n/2k)=T(1) 时,也就是 2 k 2^k 2k=1,我们得到 k= l o g 2 n log_2n log2​n 。我们将 k 值代入上面的公式,得到 T(n)=Cn+ l o g 2 n log_2n log2​n 。如果我们用大 O 标记法来表示的话,T(n) 就等于 O(nlogn)。所以归并排序的时间复杂度是 O(nlogn)。从我们的原理分析和伪代码可以看出,归并排序的执行效率与要排序的原始数组的有序程度无关,所以其时间复杂度是非常稳定的,不管是最好情况、最坏情况,还是平均情况,时间复杂度都是 O(nlogn)-----摘自-极客时间-数据结构与算法-王争

归并排序的时间复杂度已经很优秀了,但为什么我们在日常开发中却很少看到他的身影呢?我们先来分析一下归并排序的空间复杂度。

我们需要注意的是合并方法,这个方法中我们使用了一个临时数组用来存储数据,但是合并之后这个临时数组就会释放,又因为临时数组的最大长度不会超过原始数组长度n,所以归并排序的空间复杂度为:O(n)

为什么开发中很少人使用到归并排序呢?原因很简单,因为它不是一个原地排序算法,这个时候你可能会有疑惑了,什么是原地排序算法?简单来说:不通过其他空间来完成的排序,我们称它为原地排序算法,但归并排序很明显借用了一个临时数组,所以它不是一个原地排序算法,即使它的时间复杂都很稳定,使用的人也比较少。


三、快速排序

=====================================================================

1.什么是快速排序


如果要排序数组中下标从 p 到 r 之间的一组数据, 我们选择 p 到 r 之间的任意一个数据作为 pivot(分区点)。我们遍历 p 到 r 之间的数据,将小于 pivot 的放到左边, 将大于 pivot 的放到右边,将 pivot 放到中间。经过这一步骤之后, 数组 p 到 r 之间的数据就被分成了三个部分,前面 p 到 q-1 之间都是小于 pivot 的,中间是 pivot,后面的 q+1 到 r 之间是大于 pivot 的.根据分治、递归的处理思想,我们可以用递归排序下标从 p 到 q-1 之间的数据和下标从 q+1 到 r 之间的数据,直到区间缩小为 1,就说明所有的数据都有序了。(摘自-极客时间-数据结构与算法-王争)

在这里插入图片描述

快速排序的思想和归并排序有点类似,都是通过分治的思想,利用递归实现排序,只不过实现的细节有所不同,快排(快速排序)需要一个分区点,可以在数组中随便去一个元素作为分区点即可,后面就是前面讲到的概念了,归并的核心在于合并,而快排的核心在于分区点,所以我们就一起来看看在获取分区点的时候快排都干了些啥?

之前说过,快排选择一个分区点(pivot)之后,将小于分区点(pivot)的元素放左边,分区点(pivot)放中间,大于分区点(pivot)的放右边,这个一看就很好解决嘛,和归并排序一样,我先申请两个临时数组,一个存放小于分区点元素的数组,一个存放大于分区点元素的数组,这样,就能完美的解决了,非常简单,但是这个就和归并排序面临这同一样的一个问题:它不是一个原地排序算法,那如果我希望快排是一个原地排序算法呢?我们应该如何实现呢?其实也不难,我们可以参考一下选择排序:【数据结构与算法】常见的三种排序(冒泡排序、插入排序、选择排序

在这里插入图片描述

我们定义一个游标 i (数组下标) 把数组a[p-(r-1)]分成两部分,A[p-(i-1)]都是小于分区点(pivot)的,我们叫他“已排序区间”。a[(i+1) - (r-1)]都是大于分区点(pivot)元素的,我们叫他“未排序区间”,只要从未排序区间去值与分区点(pivot)进行比较,如果小于分区点(pivot),那么将此元素追加到已排序区间中(a[i]),否者不需要变动。

我还是准备了一张图给大家参考,也许大家就能明白了。

在这里插入图片描述

这是一次分区交换的结果,当把所有分区都交换完成之后,整个数组也就有序了,既然快速排序的思想已经讲的差不多了,下面我们一起来看看代码怎么实现

快排代码实现

package com.liuxing.sort;

import com.liuxing.util.DataUtil;

import com.liuxing.util.Print;

/**

  • @author liuxing007

  • @ClassName Quicksort

  • @Description 快速排序

  • 如果要排序数组中下标从 p 到 r 之间的一组数据,

  • 我们选择 p 到 r 之间的任意一个数据作为 pivot(分区点)。

  • 我们遍历 p 到 r 之间的数据,将小于 pivot 的放到左边,

  • 将大于 pivot 的放到右边,将 pivot 放到中间。经过这一步骤之后,

  • 数组 p 到 r 之间的数据就被分成了三个部分,

  • 前面 p 到 q-1 之间都是小于 pivot 的,中间是 pivot,

  • 后面的 q+1 到 r 之间是大于 pivot 的.

  • 根据分治、递归的处理思想,

  • 我们可以用递归排序下标从 p 到 q-1 之间的数据和下标从 q+1 到 r 之间的数据,

  • 直到区间缩小为 1,就说明所有的数据都有序了(摘自-极客时间-数据结构与算法-王争)

  • 时间复杂度:O(nlogn)

  • 空间复杂度:O(1)

  • 原地排序算法,但不是稳点排序算法

  • @date 2020/9/18 10:22

*/

public class Quicksort {

public static void main(String[] args) {

int[] arr = new int[]{6, 5, 4, 3, 2, 1};

// int[] arr = DataUtil.createIntArrData();

int length = arr.length;

System.out.println(“排序前数组===========”);

Print.print(arr, length);

sort(arr, length);

System.out.println(“排序后数组===========”);

Print.print(arr, length);

}

/**

  • 排序算法

  • @param arr 数组

  • @param l 数组长度

*/

private static void sort(int[] arr,int l){

sortRec(arr,0,l-1);

}

/**

  • 递归

  • @param arr

  • @param p

  • @param r

*/

private static void sortRec(int[] arr, int p, int r) {

//递归终止条件

if (p >= r){

return;

}

//获取分区点

int q = partition(arr, p, r);

//左侧递归

sortRec(arr, p, q-1);

//右侧递归

sortRec(arr, q+1, r);

}

/**

  • 分区

  • @param arr 原始数组

  • @param p 数组起始下标

  • @param r 数组结束下标

  • @return 分区下标

*/

private static int partition(int[] arr, int p, int r) {

//取最后一个元素作为分区的值

int pivot = arr[r];

int i = p;

for(int j = p; j < r; ++j) {

if (arr[j] < pivot) {

if (i == j) {

++i;

} else {

//交换位置

int tmp = arr[i];

arr[i++] = arr[j];

arr[j] = tmp;

}

}

}

//交换位置

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
img

最后

小编利用空余时间整理了一份《MySQL性能调优手册》,初衷也很简单,就是希望能够帮助到大家,减轻大家的负担和节省时间。

关于这个,给大家看一份学习大纲(PDF)文件,每一个分支里面会有详细的介绍。

image

这里都是以图片形式展示介绍,如要下载原文件以及更多的性能调优笔记(MySQL+Tomcat+JVM)!

411206117)]
[外链图片转存中…(img-jxa4q3D1-1710411206118)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
[外链图片转存中…(img-Bqzdly3o-1710411206118)]

最后

小编利用空余时间整理了一份《MySQL性能调优手册》,初衷也很简单,就是希望能够帮助到大家,减轻大家的负担和节省时间。

关于这个,给大家看一份学习大纲(PDF)文件,每一个分支里面会有详细的介绍。

[外链图片转存中…(img-RkuVO3r3-1710411206118)]

这里都是以图片形式展示介绍,如要下载原文件以及更多的性能调优笔记(MySQL+Tomcat+JVM)!

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值