ImageVerifierCode 换一换
格式:DOCX , 页数:28 ,大小:610.37KB ,
资源ID:23698620      下载积分:3 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bdocx.com/down/23698620.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(经典常用排序算法的原理及 实现以及算法分析.docx)为本站会员(b****8)主动上传,冰豆网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰豆网(发送邮件至service@bdocx.com或直接QQ联系客服),我们立即给予删除!

经典常用排序算法的原理及 实现以及算法分析.docx

1、经典常用排序算法的原理及 实现以及算法分析本文介绍了7 种最经典、最常用的排序算法,分别是:冒泡排序、插入排序、选择排序、归并排序、快速排序、桶排序、计数排序、基数排序。对应的时间复杂度如下所示:排序算法时间复杂度是否基于比较冒泡、插入、选择O(n2)快排、归并O(nlogn)桶、计数、基数O(n)整篇文章的主要知识提纲如图所示:1. 排序算法分析学习排序算法除了学习它的算法原理、代码实现之外,最重要的是学会如何评价、分析一个排序算法。分析一个排序算法通常从以下几点出发。1.1. 执行效率而对执行效率的分析,一般从这几个方面来衡量: 最好情况、最坏情况、平均情况除了需要给出这三种情况下的时间复

2、杂度还要给出对应的要排序的原始数据是怎么样的。 时间复杂度的系数、常数、低阶大O时间复杂度反应的是算法时间随n 的一个增长趋势,比如O(n2) 表示算法时间随 n 的增加,呈现的是平方的增长趋势。这种情况下往往会忽略掉系数、常数、低阶等。但是实际开发过程中,排序的数据往往是10 个、100 个、1000 个这样规模很小的数据,所以在比较同阶复杂度的排序算法时,这些系数、常数、低阶不能省略。 比较次数和交换(或移动)次数在基于比较的算法中,会涉及到元素比较和元素交换等操作。所以分析的时候,还需要对比较次数和交换次数进行分析。1.2. 内存消耗内存消耗其实就是空间复杂度。针对排序算法来说,如果该排

3、序算法的空间复杂度为O(1),那么这个排序算法又称为原地排序。1.3. 稳定性是什么稳定性是指待排序的序列中存在值相等的元素。在排序之后,相等元素的前后顺序跟排序之前的是一样的。为什么我们将排序的原理和实现排序时用的大部分都是整数,但是实际开发过程中要排序的往往是一组对象,而我们只是按照对象中的某个key 来进行排序。比如一个对象有两个属性,下单时间和订单金额。在存入到数据库的时候,这些对象已经按照时间先后的顺序存入了。但是我们现在要以订单金额为主key,在订单金额相同的时候,以下单时间为key。那么在采用稳定的算法之后,只需要按照订单金额进行一次排序即可。比如有这么三个数据,第一个数据是下单

4、时间、第二数据是订单金额:(20200515、20)、(20200516、10)、(20200517、30)、(20200518、20)。在采用稳定的算法之后,排序的情况如下:(20200516、10)、(20200515、20)、(20200518、20)、(20200517、30)可以发现在订单金额相同的情况下是按订单时间进行排序的。2. 经典的常用排序算法2.1. 冒泡排序冒泡排序就是依次对两个相邻的元素进行比较,然后在不满足大小条件的情况下进行元素交换。一趟冒泡排序下来至少会让一个元素排好序(元素排序好的区域相当于有序区,因此冒泡排序中相当于待排序数组分成了两个已排序区间和未排序区间)

5、。因此为了将 n 个元素排好序,需要 n-1 趟冒泡排序(第 n 趟的时候就不需要)。下面用冒泡排序对这么一组数据4、5、6、3、2、1,从小到大进行排序。第一次排序情况如下:可以看出,经过一次冒泡操作之后,6 这个元素已经存储在正确的位置上了,要想完成有所有数据的排序,我们其实只需要 5 次这样的冒泡排序就行了。图中给出的是带第 6 次了的,但是第 6 次其实没必要。2.1.1. 优化使用冒泡排序的过程中,如果有一趟冒泡过程中元素之间没有发生交换,那么就说明已经排序好了,可以直接退出不再继续执行后续的冒泡操作了。2.1.2. 实现下面的冒泡排序实现是优化之后的:/* * 冒泡排序: * 以升

6、序为例,就是比较相邻两个数,如果逆序就交换,类似于冒泡; * 一次冒泡确定一个数的位置,因为要确定 n-1 个数,因此需要 n-1 * 次冒泡; * 冒泡排序时,其实相当于把整个待排序序列分为未排序区和已排序区 */public void bubbleSort(int arr, int len) / len-1 趟 for (int j = 0; j len-1; j+) int sortedFlag = 0; / 一趟冒泡 for (int i = 0; i arri+1) int temp = arri; arri = arri+1; arri+1 = temp; sortedFlag =

7、 1; / 该趟排序中没有发生,表示已经有序 if (0 = sortedFlag) break; 2.1.3. 算法分析 冒泡排序是原地排序。因为冒泡过程中只涉及到相邻数据的交换,相当于只需要开辟一个内存空间用来完成相邻的数据交换即可。 在元素大小相等的时候,不进行交换,那么冒泡排序就是稳定的排序算法。 冒泡排序的时间复杂度。 当元素已经是排序好了的,那么最好情况的时间复杂度是 O(n)。因为只需要跑一趟,然后发现已经排好序了,那么就可以退出了。 当元素正好是倒序排列的,那么需要进行 n-1 趟排序,最坏情况复杂度为 O(n2)。 一般情况下,平均时间复杂度是 O(n2)。使用有序度和逆序度

8、的方法来求时间复杂度,冒泡排序过程中主要是两个操作:比较和交换。每交换一次,有序度就增加一,因此有序度增加的次数就是交换的次数。又因为有序度需要增加的次数等于逆序度,所以交换的次数其实就等于逆序度。因此当要对包含 n 个数据的数组进行冒泡排序时。最坏情况下,有序度为 0 ,那么需要进行 n*(n-1)/2 次交换;最好情况下,不需要进行交换。我们取中间值 n*(n-1)/4,来表示初始有序度不是很高也不是很低的平均情况。由于平均情况下需要进行 n*(n-1)/4 次交换,比较操作肯定比交换操作要多。但是时间复杂度的上限是 O(n2),所以平均情况下的时间复杂度就是 O(n2)。这种方法虽然不严

9、格,但是很实用。主要是因为概率的定量分析太复杂,不实用。(PS:我就喜欢这种的)”2.2. 插入排序插入排序中将数组中的元素分成两个区间:已排序区间和未排序区间(最开始的时候已排序区间的元素只有数组的第一个元素),插入排序就是将未排序区间的元素依次插入到已排序区间(需要保持已排序区间的有序)。最终整个数组都是已排序区间,即排序好了。*假设要对 n 个元素进行排序,那么未排序区间的元素个数为 n-1,因此需要 n-1 次插入。插入位置的查找可以从尾到头遍历已排序区间也可以从头到尾遍历已排序区间。如图所示,假设要对 4、5、6、1、3、2进行排序。左侧橙红色表示的是已排序区间,右侧黄色的表示未排序

10、区间。整个插入排序过程如下所示2.2.1. 优化 采用希尔排序的方式。 *使用哨兵机制。*比如要排序的数组是2、1、3、4,为了使用哨兵机制,首先需要将数组的第 0 位空出来,然后数组元素全都往后移动一格,变成0、2、1、3、4。那么数组 0 的位置用来存放要插入的数据,这样一来,判断条件就少了一个,不用再判断 j = 0 这个条件了,只需要使用 arrj arr0 的条件就可以了。因为就算遍历到下标为 0 的位置,由于 0 处这个值跟要插入的值是一样的,所以会退出循环,不会出现越界的问题。2.2.2. 实现这边查找插入位置的方式采用从尾到头遍历已排序区间,也没有使用哨兵。/* * 插入排序:

11、 * 插入排序也相当于把待排序序列分成已排序区和未排序区; * 每趟排序都将从未排序区选择一个元素插入到已排序合适的位置; * 假设第一个元素属于已排序区,那么还需要插入 len-1 趟; */public void insertSort(int arr, int len) / len-1 趟 for (int i = 1; i = 0; j-) if (arrj temp) arrj+1 = arrj; else break; arrj+1 = temp; 2.2.3. 算法分析 插入排序是原地算法。因为只需要开辟一个额外的存储空间来临时存储元素。 当比较元素时发现元素相等,那么插入到相等元

12、素的后面,此时就是稳定排序。也就是说只有当有序区间中的元素大于要插入的元素时才移到到后面的位置,不大于(小于等于)了的话直接插入。 插入排序的时间复杂度。 待排序的数据是有序的情况下,不需要搬移任何数据。那么采用从尾到头在已排序区间中查找插入位置的方式,最好时间复杂度是 O(n)。 待排序的数据是倒序的情况,需要依次移动 1、2、3、.、n-1 个数据,因此最坏时间复杂度是 O(n2)。 平均时间复杂度是 O(n2)。因此将一个数据插入到一个有序数组中的平均时间度是 O(n),那么需要插入 n-1 个数据,因此平均时间复杂度是 O(n2)最好的情况是在这个数组中的末尾插入元素的话,不需要移动数

13、组,时间复杂度是 O(1),假如在数组开头插入数据的话,那么所有的数据都需要依次往后移动一位,所以时间复杂度是 O(n)。往数组第 k 个位置插入的话,那么 kn 这部分的元素都需要往后移动一位。因此此时插入的平均时间复杂度是 O(n)。2.2.4. VS 冒泡排序冒泡排序和插入排序的时间复杂度都是 O(n2),都是原地稳定排序。而且冒泡排序不管怎么优化,元素交换的次数是一个固定值,是原始数据的逆序度。插入排序是同样的,不管怎么优化,元素移动的次数也等于原始数据的逆序度。但是,从代码的实现上来看,冒泡排序的数据交换要比插入排序的数据移动要复杂,冒泡排序需要 3 个赋值操作,而插入排序只需要一个

14、赋值操作。所以,虽然冒泡排序和插入排序在时间复杂度上都是 O(n2),但是如果希望把性能做到极致,首选插入排序。其实该点分析的主要出发点就是在同阶复杂度下,需要考虑系数、常数、低阶等。2.3. 选择排序选择排序也分为已排序区间和未排序区间(刚开始的已排序区间没有数据),选择排序每趟都会从未排序区间中找到最小的值(从小到大排序的话)放到已排序区间的末尾。2.3.1. 实现/* * 选择排序: * 选择排序将待排序序列分成未排序区和已排序区; * 第一趟排序的时候整个待排序序列是未排序区; * 每一趟排序其实就是从未排序区选择一个最值,放到已排序区; * 跑 len-1 趟就好 */public

15、void switchSort(int arr, int len) / len-1 趟,0-i 为已排序区 for (int i = 0; i len-1; i+) int minIndex = i; for (int j = i+1; j len; j+) if (arrj =r,不再递归下去了。整个实现过程是先调用_mergeSort()函数将两部分分别排好序,之后再使用数组合并的方式将两个排序好的部分进行合并。/* * 归并排序 */public void mergeSort(int arr, int len) _mergerSort(arr, 0, len-1);private voi

16、d _mergerSort(int arr, int begin, int end) if (begin = end) return; _mergerSort(arr, begin, (begin+end)/2); _mergerSort(arr, (begin+end)/2 + 1, end); merge(arr, begin, end); return;private void merge(int arr, int begin, int end) int copyArr = new intend-begin+1; System.arraycopy(arr, begin, copyArr,

17、 0, end-begin+1); int mid = (end - begin + 1)/2; int i = 0; / begin - mid 的指针 int j = mid; / mid - end 的指针 int count = begin; / 合并之后数组的指针 while (i = mid-1 & j = end - begin) arrcount+ = copyArri copyArrj ? copyArri+ : copyArrj+; while (i = mid-1) arrcount+ = copyArri+; while (j = j。在返回 pivot 的下标 q 之

18、后,再根据分治的思想,将 begin 到 q-1 之间的数据和下标 q+1 到 end 之间的数据进行递归。这边一定要 q-1 和 q+1 而不能是 q 和 q+1 是因为:考虑数据已经有序的极端情况,一开始是对 begin 到 end;当分区之后 q 的位置还是 end 的位置,那么相当于死循环了。最终,当区间缩小至 1 时,说明所有的数据都有序了。如果用递推公式来描述上述的过程的话,递推公式:quick_sort(begin.end) = quick_sort(begin.q-1) + quick_sort(q+1.end),终止条件是:begin = end。将这两个公式转化为代码之后,

19、如下所示:/* * 快速排序 */public void quickSort(int arr, int len) _quickSort(arr, 0, len-1);/ 注意边界条件private void _quickSort(int arr, int begin, int end) if (begin = end) return; / 一定要是 p-1! int p = partition(arr, begin, end); / 先进行大致排序,并获取区分点 _quickSort(arr, begin, p-1); _quickSort(arr, p+1, end);private int

20、 partition(int arr, int begin, int end) int pValue = arrend; / 整两个指针,两个指针都从头开始 / begin - i-1(含 i-1):小于 pValue 的区 / i - j-1(含 j-1):大于 pValue 的区 / j - end:未排序区 int i = begin; int j = begin; while (j = end) if (arrj = pValue) int temp = arrj; arrj = arri; arri = temp; i+; j+; else j+; return i-1;2.5.2.

21、 优化 由于分区点很重要(为什么重要见算法分析),因此可以想方法寻找一个好的分区点来使得被分区点分开的两个分区中,数据的数量差不多。下面介绍两种比较常见的算法: *三数取中法。就是从区间的首、尾、中间分别取出一个数,然后对比大小,取这 3 个数的中间值作为分区点。*但是,如果排序的数组比较大,那“三数取中”可能不够了,可能就要“五数取中”或者“十数取中”,也就是间隔某个固定的长度,取数据进行比较,然后选择中间值最为分区点。 随机法。随机法就是从排序的区间中,随机选择一个元素作为分区点。随机法不能保证每次分区点都是比较好的,但是从概率的角度来看,也不太可能出现每次分区点都很差的情况。所以平均情况下,随机法取分区点还是比较好的。 递归可能会栈

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1