大佬教程收集整理的这篇文章主要介绍了Java集合与数据结构——七大排序算法的实现,大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。
本文内容介绍大纲
排序c;就是使一串记录c;按照其中的某个或某些关键字的大小c;递增或递减的排列起来的操作。
平时的上下文中c;如果提到排序c;通常指的是排升序(非降序)。
通常意义上的排序c;都是指的原地排序(in place sort)
两个相等的数据c;如果经过排序后c;排序算法能保证其相对位置不发生变化c;则我们称该算法是具备稳定性的排序算法。
算法演示:
@H_696_163@1.原理整个区间被分为
1.有序区间
2.无序区间
每次选择无序区间的第一个元素c;在有序区间内选择合适的位置插入
@H_696_163@2.基本思想直接插入排序是一种简单的插入排序法c;其基本思想是:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中c;直到所有的记录插入完为止c;得到一个新的有序序列 。
实际中我们玩扑克牌时c;就用了插入排序的思想
我们来说一下 直接插入排序的具体步骤:
1.定义一个 变量 i c;i 从这个数组中的第二个元素开始遍历
2.定义 一个变量 j c; j = i - 1 .如果 arr[ i ] 比 arr [ j] 小的话c;每次都把 arr [ j+1 ] = arr [ j ]c;相当于把 i 之前 比 arr [ i ] 大的数字全都向后移动一位c;直到遇到 arr [ j ] < arr [ i ],此时 arr [j+1] = arr [ i ].
3.如果j向前遍历c;直到 j<0 时 也没有满足 arr[ i ] < arr [ j], j 向前的遍历结束 c; arr [ j+1 ] = arr [ i ].
@H_696_163@3.代码展示 稳定性判断: 根据上面的思路我们进行遍历c;发现4 4 的位置并没有进行交换 c;所以 直接插入排序是稳定的.4.最后完成遍历c;排序完成.
还有一种判断稳定性的方法:
还有通过这个代码我们发现c;这个排序也可以变成不稳定的c;
在这样的情况下c;相同的元素 在比较时就会发生交换c;排序变成不稳定的了.我们可以得到一个结论:
一个稳定的排序c;可以实现为不稳定的排序
@H_696_163@4.性能分析但是一个本身就不稳定的排序c;就不可能实现为稳定的排序
时间复杂度
最好情况下:
最坏情况下:
我们同样得到一个结论:
空间复杂度
稳定性
算法演示:
希尔排序法又称缩小增量法。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
@H_696_163@1.基本思想2.但插入排序一般来说是低效的c;因为插入排序每次只能将数据移动一位。
1. 先选定一个小于N的整数gap作为第一增量c;然后将所有距离为gap的元素分在同一组c;并对每一组的元素进行直接插入排序。然后再取一个比第一增量小的整数作为第二增量c;重复上述操作…
为什么要让gap由大到小呢?
@H_696_163@*注意点gap越大c;数据移动得越快;gap越小c;数据移动得越慢。开始让gap较大c;可以让数据更快得移动到自己对应的位置附近c;减少移动次数.
1.希尔排序是对直接插入排序的优化。
2. 当gap > 1时都是预排序c;目的是让数组更接近于有序。当gap == 1时c;数组已经接近有序的了c;这样就会很快。这样整体而言c;可以达到优化的效果。我们实现后可以进行性能测试的对比。
我们来将整个排序的 思路走一遍:
下面是 我们要进行排序的数组
将数组中的元素进行分组c;每组中的元素 gap 间隔为3c; 我用不同颜色进行分组.
gap ==3 c;分组完之后c;我们将每一组中的数据进行排序将数组中的元素进行分组c;每组中的元素 gap 间隔为2c; 我用不同颜色进行分组.
gap == 2 c;分组完之后c;我们将每一组中的数据进行排序将数组中的元素进行分组c;每组中的元素 gap 间隔为1c; 此时对整体进行排序.
整体排完序后c;希尔排序完成.
@H_696_163@2.代码展示 @H_696_163@3.增量 gap 的选取每一组排序我们都用的是 直接插入排序.
选自 《数据结构》清华大学出版
gap 的值 没有除 1 以外 的公因子c;并且最后一个增量值 必须为 1 .我们只能尽量 追求gap 没有公因子c; 最后 要 +1.
我们可以这样取 gap c;使 gap 最后为 1.
gap = arr@H_450_425@.length@H_450_425@;
while@H_450_425@(gap>1@H_450_425@)@H_450_425@{
gap = gap/3+1@H_450_425@; // 加 1 保证最后一个序列为1 c;除几都行
@H_450_425@}
@H_696_163@4.性能分析
时间复杂度
最坏、最好情况下
由于 gap 每次取值都不同c;所以算起来十分复杂c;但是我们仍然能够得到一些数据…
所以我们就这样认为最好情况下时间复杂度 O(n^ 1.3)
最坏情况下时间复杂度 O(n^ 1.5)
空间复杂度
没有借助其他的辅助空间c;所以空间复杂度 为 O(1)
稳定性
在这个排序中 发生了跳跃式的交换c;所以这个排序不是稳定的.
算法演示:
@H_696_163@1.基本思想数组从头开始遍历 c; i= 0开始c;i 后面的每一个元素 arr [ j ] 都与 arr[i] 进行比较c;如果 arr [ i ]> arr [ j ] ,那么就进行交换.
我们根据思路来 走一下排序的过程.
我们要对 这个数据进行排序
开始进行排序
@H_696_163@2.代码展示 @H_696_163@3.性能分析时间复杂度
最坏情况下: O(n^2)
最好情况下: O(n^2)
空间复杂度
没有借助辅助空间c;所以空间复杂度为 O ( 1 )
稳定性
因为在排序的过程中发生了跳跃式交换c;所以这种排序不是稳定的.
算法演示:
@H_696_163@1.基本思想从小到大排序 —— 升序 建立大堆
从大到小排序 —— 降序 建立小堆
思路: 以升序 为 例
0.先将数组 调整为一个 大堆 c;建立一个大堆
2. 此时从第一个到 倒数第二个再次调整c;调整完后将堆顶元素 与倒数第二个元素交换c;按照这样的逻辑规律c;循环直到 有序.
我们以实际 例子说明…
下面以数组 [5, 7, 9 , 3, 1, 8c;6,2] 为例进行从小到大排序的演示:
0.调整为大堆
1.首尾交换
2.向下调整
最后我们排完序了重复此操作直到全部有序
如何将一个数组转换成一个堆呢?
@H_696_163@2.建堆操作下面我们给出一个数组c;这个数组逻辑上可以看做一颗完全二叉树c;但是还不是一个堆c;现在我们通过算法c;把它构建成一个堆。
根节点左右子树不是堆c;我们怎么调整呢?这里我们从倒数的第一个非叶子节点的子树开始调整c;一直调整到根节点的树c;就可以调整成堆。
将一个二叉树 调整为一个 大根堆
这棵二叉树调整为 大根堆 必须将 每颗子树都调整为大根堆.
@H_696_163@3.向下调整思想 步骤:
parent —> 根节点下标
child —> 孩子节点下标
1.从最后一棵子树进行调整.
2. 每颗子树从根节点向下调整c;如果左右孩子节点的最大值比这个根节点大c;那么值互换c;然后 parent 指向 child ,child = 2* parent + 1c; 继续向下调整c;直到 下标child 超出二叉树 范围.
代码实现:
这就是 向下调整的完整过程.
我们来看整体堆排序的代码展示:
@H_696_163@4.代码展示 @H_696_163@5.性能分析时间复杂度
最好最坏情况下c;都是 O(n* logn)
空间复杂度
没有借助外部空间c;空间复杂度为O(1)
稳定性
发生了跳跃式的交换c;所以是不稳定的.
算法演示:
@H_696_163@1.基本思想比较相邻的元素。如果第一个比第二个大c;就交换他们两个。
对每一对相邻元素作同样的工作c;从开始第一对到结尾的最后一对。
针对所有的元素重复以上的步骤c;除了最后一个。 我们也可以找到规律:
这个数组一共有 10个数字
第 1 个数字比较了 9次c; 第 2 个数字比较了 8 次…
第 i 个数字 比较 10 - i 次
持续每次对越来越少的元素重复上面的步骤c;直到没有任何一对数字需要比较。
我们来 走一遍 冒泡排序的思路:
之后对每一个数字都从头开始比较相邻元素…直到全部排序完成. @H_696_163@2.代码展示 @H_696_163@3.性能分析时间复杂度
最好、最坏情况下都是 O(n^2),在优化下c;最好情况是O(n).
空间复杂度
没有借助辅助空间c;所以空间复杂度为O(1)
稳定性
都是相邻元素之间进行排序c;所以这个排序是稳定的.
1.从待排序区间选择一个数c;作为基准值(pivot);通常为最左边的数字.
2.Partition: 遍历整个待排序区间c;将比基准值小的(可以包含相等的)放到基准值的左边c;将比基准值大的(可以包含相等的)放到基准值的右边;
@H_696_163@1. Hoare 法3. 采用分治思想c;对左右两个小区间按照同样的方式处理c;直到小区间的长度 == 1c;代表已经有序c;或者小区间的长度 == 0c;代表没有数据。
左边第一个数字下标定义为 start 右边第一个数字下标定义为 endc;key 为第一个数字
start 在向后走c;找到比 key 大的位置
重复此过程…
@H_696_163@2.挖坑法直到 start 和 end 相遇c; 将该位置的值 与 key 交换.
左边第一个数字下标定义为 start 右边第一个数字下标定义为 end
先将第一个数据放到 临时变量 tmp 中c;形成一个坑位
重复上面的两个过程…
最后 start 和 end 相互遇见c;将 tmp 的值 放入最后一个 相遇的坑位.
我们来走一遍 挖坑法 的具体思路:
快速排序的优化
1.选择基准值很重要c;通常使用几数取中法
我们如果选取的 在基准的数值正好是 这组数据的中位数c;每次都是平均 分c;那么此时 时间复杂度最小c;但是实际情况中通常没有那么巧合c;所以我们为了追求尽可能小的 时间复杂度c;取 这组数据 头 、尾 、 中间三个数字中的中间值作为 基准.
我们在实现 paitition 函数时c;要满足这个条件:
arr [ mid] <= arr [ start ] <= arr [ end ]
2.partition 过程中把和基准值相等的数也选择出来
3.待排序区间小于一个阈值时(例如 48)c;使用直接插入排序
随着递归的进行,数据的区间在缩小,区间的数据也在慢慢趋近于有序…
@H_696_163@3.非递归思路1.调用 partition 之后c;找到了 pivot
2.把当前 pivot 的左边区间 和右边区间 的下标放入栈中
3.判断栈是否为 空c;不为空c;弹出栈顶2个元素c;注意: 放的顺序 决定了 取出的顺序中第一个元素是给的 high 还是 low.
4.再进行 partion
什么时候 入栈?
当这个区间 最起码有 2个元素的时候
代码展示:
时间复杂度
最好的情况下c;选完基准之后都均分c;此时时间复杂度为O(n*logn) 最坏的情况下c; 数组为一个有序的数组c;我们要逆序c;此时时间复杂度为 O(n^2).
空间复杂度
最好情况下是每次都二分c;所以空间复杂度为O(logn),最坏情况下为 O(n),所以 空间复杂度为 O(logn)~ O(n).
稳定性
排序时发生了跳跃式交换c;所以是不稳定的
归并算法演示
@H_696_163@1.原理总览归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并c;得到完全有序的序列;即先使每个子序列有序c;再使子序列段间有序。若将两个有序表合并成一个有序表c;称为二路归并。
根据思路我们来将 归并排序走一遍:
2.按其拆分的方式c;对其对应的两个元素进行排序并合并成一组.
3.对合并过的组c;每两组再次进行合并
在这个思路中 最重要的 就只有两步:
@H_696_163@2.合并两个有序数组2.合并 c;将各个元素有序合并.
我们可以根据 start、mid、end 得到两个数组的区间
[ start , mid ] ---- [ mid+1 , end ]
构建一个 辅助的数组空间.
我们在排序时c;有以下几种情况
两个数组都未遍历完c; s1<= e1 && s2<=e2 , 两个同时遍历c;谁小往辅助数组放元素.放完之后 c;辅助元素的下标 ++ c;放到数组元素也 ++.
有一个数组遍历完了c;直接在 已经排好序的数组之后接上 未遍历完的.
合并数组的代码展示:
@H_696_163@3.代码展示归并排序的完整代码展示:
@H_874_1101@
@H_696_163@4.性能分析时间复杂度
因为要进行二分拆解c;所以最好、最坏情况下都是 O(n* logn)
空间复杂度
由于在合并有序数组是借助了 辅助空间c;所以 空间复杂度为 O(n).
稳定性
每次排序都是相邻的元素之间比较c;所以是稳定的.
我们学习了这几种基于比较的排序算法c;下面我们来进行总结一下.
我们学的排序都是内部排序c;什么是内部排序呢? 就是把数据放在内存中 进行排序 .
内排序:数据量相对少一些c;可以放到内存中进行排序。
上面介绍的排序算法均是在内存中进行的c;对于数据量庞大的序列c;上面介绍的排序算法都束手无策c;而归并排序却能胜任这种海量数据的排序。
外部排序:排序过程需要在磁盘等外部存储进行的排序
前提:内存只有 1Gc;需要排序的数据有 100G
因为内存中因为无法把所有数据全部放下c;所以需要外部排序c;而归并排序是最常用的外部排序
1.先把文件切分成 200 份c;每个 512 M
好了今天的知识就分享到这里c;希望大家多多练习c;熟练掌握c;感谢大家的欣赏与关注!!
谢谢欣赏!!
以上是大佬教程为你收集整理的Java集合与数据结构——七大排序算法的实现全部内容,希望文章能够帮你解决Java集合与数据结构——七大排序算法的实现所遇到的程序开发问题。
如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。