【数据结构与算法】高级排序(希尔排序、归并排序、快速排序)完整思路,并用代码封装排序函数

为了方便大家理解,我用一个例子来展示一个完整的希尔排序过程,首先数据的初始状态如图所示,这里为了更好地体现希尔排序的优点,我特地把值较大的元素放到了靠左的位置,把值较小的元素放到了靠右的位置

在这里插入图片描述

该数组长度为8,因此我们设置初始的增量为 8 / 2 = 4,那么该数组的分组情况如下图所示:

在这里插入图片描述

图中颜色相同的元素为一组,每组内的各个元素间隔都为4,现在对每个组内进行从小到大排序,排序结果如下图所示:

在这里插入图片描述

此时我们将增量缩小一半,即 4 / 2 = 2,同样的,现在将所有元素重新组合,把所有间隔为2的元素视作一组,分组结果如下图所示:

在这里插入图片描述

图中颜色相同的元素为一组,每组内的各个元素间隔都为2,现在对每个组内进行从小到大排序,排序结果如下图所示:

在这里插入图片描述

我们继续将增量缩小一半,即 2 / 2 = 1,同样的,现在将所有元素重新组合,把所有间隔为1的元素视作一组,此时所有的元素都为同一组了,就相当于对所有的数据进行普通的插入排序,我们可以看到,对比最开始的数据,总得来说,小的值都比较靠左了,大的值也都比较靠右了,这样排序起来效率就很高了。结果如下图所示:

在这里插入图片描述

接下来用一个动图,演示一下完整的希尔排序全过程

在这里插入图片描述

了解完了希尔排序的实现过程,我们现在用代码来封装一下

function shellSort(arr) {

// 1. 获取数组长度

let length = arr.length

// 2.获取初始的间隔长度

let interval = Math.floor(length / 2)

// 3. 不断地缩小间隔的大小,进行分组插入排序

while(interval >= 1) {

// 4. 从 arr[interval] 开始往后遍历,将遍历到的数据与其小组进行插入排序

for(let i = interval; i < length; i++) {

let temp = arr[i]

let j = i

while(arr[j - interval] > temp && j - interval >= 0) {

arr[j] = arr[j - interval]

j -= interval

}

arr[j] = temp

}

// 5. 缩小间隔

interval = Math.floor(interval / 2)

}

return arr

}

来用刚才举得例子来验证一下我们封装的希尔排序是否正确

let arr = [63, 76, 13, 44, 91, 8, 82, 3]

let res = shellSort(arr)

console.log(res)

/* 打印结果

[3, 8, 13, 44, 63, 76, 82, 91]

*/

上述情况中,希尔排序最坏情况下的时间复杂度为O(n²)。其实希尔排序的时间复杂度跟增量也有关系,我们上面是通过数组长度一直取一半获取的增量,其实还有一些别的增量规则,可以使得希尔排序的效率更高,例如Hibbard增量序列Sedgewick增量序列,本文就不对这两种增量做过多的讲解了,大家可以去网上搜索一下。

二、归并排序

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

归并排序的实现是使用了一种分而治之的思想,即将一个数组不断地通过两两分组的方式进行比较大小,最后直到所有元素都被分到一组里,那自然就是整体有序的了。

我们来看一下归并排序的主要思路,首先有如下图所示排列的一组数据:

在这里插入图片描述

首先从左往右,每两个元素视为一组,组合前要分别判断一下这两个元素的大小,小的在左,大的右,如图所示

在这里插入图片描述

继续再取两个元素组成一组并比较大小,如图所示:

在这里插入图片描述

继续上一个步骤,如图所示:

在这里插入图片描述

此时,原数组的所有元素都被两两分组完毕了,现在整个数组的长度变成了3,如下图所示:

在这里插入图片描述

此时,我们要重新从左向右,每次取两个元素组成一组,同时分别比较两个元素内的所有子元素的大小,因为此时的两个元素内部是有序的,所以我们比较两者大小的话,只需要每次比较数组的第一个元素即可,过程如下图所示:

在这里插入图片描述

此时原数组中只剩下一个元素了,所以就不对其做任何组合处理了,此时的数组是这样的:

在这里插入图片描述

此时的数组内只有两个元素了,所以我们只需要不断比较两个元素内部的子元素大小,即可获得完整的有序数组了,过程如下图所示:

在这里插入图片描述

这就是一个完整的归并排序的过程,接下来我们用代码来实现一下吧

function mergeSort(arr) {

// 将所有元素不断地两两组合,直到所有元素都被组合成一个组

while(arr.length > 1){

// 获取一下遍历前的数组长度,方便下面判断需要组合几次

let length = arr.length

// 创建空的新数组,用于存放所有组合后的元素

let next_arr = []

// 取两个元素进行组合,一共取 length / 2 次

for(let i = 0; i < Math.floor(length / 2); i++){

// 取出第一个元素

let left = [].concat(arr.shift())

// 取出第二个元素

let right = [].concat(arr.shift())

// 创建另一个新的空数组,用于存放组合后的所有元素

let new_arr = []

// 取两个数组中头部最小的值放到新数组中,直到一个数组为空

while(left.length > 0 && right.length > 0){

let min = left[0] > right[0]? right.shift() : left.shift()

new_arr.push(min)

}

// 将合并好的数组添加到新的数组中

next_arr.push(new_arr.concat(left.length == 0? right : left))

}

// 判断是否有一个未成组的数组

if(arr.length == 1) next_arr.push(arr[0]);

// 将所有组合后的元素构成的新数组作为下一次遍历的对象

arr = next_arr

}

// 返回完整有序数组

return arr[0]

}

我们来使用一下该方法,看看是否正确,为了方便大家理解,我在归并排序的函数里加了一条打印的代码,可以看到每次遍历后的数组情况,结果如下

let arr = [19, 97, 9, 17, 1, 8]

mergeSort(arr)

/* 打印结果:

第一次组合后:[ [ 19, 97 ], [ 9, 17 ], [ 1, 8 ] ]

第二次组合后:[ [ 9, 17, 19, 97 ], [ 1, 8 ] ]

第三次组合后:[ [ 1, 8, 9, 17, 19, 97 ] ]

*/

查看代码我们不难发现,归并排序运行起来非常得占内存,因为在组合的过程中,我们不断得在创建新的数组,然后又进行合并。但其比较次数却非常得少,只在每次合并元素时进行比较,因此归并排序的效率还是非常得高的。

三、快速排序

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

快速排序相信大家一定不陌生,就算没用过也一定听过,拥有这么大的名声,那么它的排序效率一定很高。而且快速排序也是面试中经常会被问到的,可能会让你当场手写哦~所以大家一定要掌握它的核心思想

快速排序也用到了分而治之的思想,它的实现思路非常得有意思:

  1. 先选一个元素作为基点pivot

  2. 将其余元素中所有比pivot小的值放到pivot的左边;将所有比pivot大的值放到pivot的右边

  3. 然后分别对pivot左边的所有元素、pivot右边的所有元素从步骤1开始排序依次,直到所有元素完整有序

思路看着很简单,那么我们来用一个例子具体看一下快速排序的全过程吧

首先有这样一组数据,如下图所示:

在这里插入图片描述

首先我们要选取一个元素作为基点pivot,最后排序完后,pivot左边的所有元素都是小于pivot的,右边的所有元素都是大于pivot的,因此我们希望的是尽量是两边平均一下,所以这里将采用一种方式寻找到一个大概的中点,即取数组两头的索引,分别为left 、right,再取一个中点 center,结果如下图:

在这里插入图片描述

然后在这三个元素中找到一个中等大小的值,并将其放到数组的开头位置,如下图所示:

在这里插入图片描述

到此,我们就可以将该数组的第一个元素作为此次遍历的基点pivot了,同时,我们将引入两个指针,即 ij,分别指向 left 和 right,如图所示

在这里插入图片描述

接下来就可以进行遍历了,这里我们把遍历过程称为填坑法,因为现在我们取到了数组的第一个值为pivot,所以可以假设这个位置上没有元素了,留了一个坑,需要我们将别的元素填进来。所以我们要从指针 j 开始往右寻找到一个比pivot小的值,若找到了,则将找到的值填到坑里,但要注意的是,指针 j 不能找到 指针 i 的左边去,即当指针 j 与 指针 i 重合时就停止移动。

过程如下图所示:

在这里插入图片描述

此时我们可以看到,指针 j 找到了一个小于pivot的值 8,并将找到的值填到了坑里,那么此时指针 j 所指向的位置就留下了一个坑,又需要别的元素来填坑,所以此时我们就要让指针 i 向右找一个大于pivot的值,并将值填到坑里,同样的,指针 i 也不能找到指针 j 的右边,即当指针 i 与 指针 j 重合时就停止移动。

过程如下图所示:

在这里插入图片描述

指针 i 找到了一个大于pivot的值 97 并将其填到了坑里,此时指针 i 所在的位置就留下了一个坑,因此我们又要让指针 j 往左找小于pivot的值并填到坑里,过程如图所示:

在这里插入图片描述

紧接着,指针 i 又要向右找大于pivot的值,但是移动了两个位置都没有找到,并且此时指针 i 指针 j 重合了,此时我们只需要将pivot填入到坑里就实现了pivot左边的所有元素小于它,右边所有的元素都大于它了,如图所示:

在这里插入图片描述

接下来的操作,就是我们要单独对此时pivot的左边所有元素和右边的所有元素进行上述的一系列操作,就可以实现快速排序了。本例中的左右两边区域的元素个数都是小于等于3个的,因此直接将这几个值互相进行比较大小比较方便,过程如下图:

在这里插入图片描述

了解了快速排序的实现思路,我们来用代码来实现一下

function quickSort(arr) {

// 两个数据进行交换

function exchange(v1, v2) {

let temp = arr[v1]

最后

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

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

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

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**

[外链图片转存中…(img-CKfpD5YX-1715792612349)]

[外链图片转存中…(img-aspDnWx7-1715792612349)]

[外链图片转存中…(img-lzB3H9HA-1715792612349)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值