Java集合框架之算法

Java集合框架中,还有两个工具类值得关注:Collections和Arrays。对于一个集合或数组,有很多必要的操作,比如查找、排序、反转、随机打乱、求最大值最小值等等。其中查找、排序都要用到合适的算法,以便快速完成这些操作。

1.查找

Collections和Arrays中的查找都是二分查找法。二分查找法基于一个按升序排列的元素列表,分为三部分:中值前列表、中值、中值后列表。如果是中值,直接返回,如果元素比中值大,就把“中值后列表”变成新列表,反之,把“中值前列表”变成新列表。然后对新列表继续上述操作。一直循环下去,找到为止。在最坏的情况下,需要查找log2n次,所以二分查找法的时间复杂度是O(logn)。

所以,Collections中的二分查找法只能针对List,不针对Set。List的实现又分为是不是RandomAccess。RandomAccess是一个标记接口,ArrayList有这个标记,而LinkedList没有这个标记。这两者的二分查找实现有细微的区别,如果是RandomAccess,就会基于数组下标进行二分查找,如果不是,就要依赖外部迭代器进行二分查找,显然通过数组下标查找效率会更高。

2.排序

Collections的排序方法都是调用Arrays的排序方法。对于Arrays中的排序方法,分为两类,一类是基本数据类型,一类是对象类型。对于基本数据类型采用的是双轴快速排序(Dual-Pivot QuickSort),是一种改进的快速排序算法,早期版本是相对传统的快速排序。对于对象类型则是使用TimSort,思想上也是一种归并和二分插入排序(binarySort)结合的优化排序算法。TimSort 并不是 Java 的独创,简单说它的思路是查找数据集中已经排好序的分区(这里叫 run),然后合并这些分区来达到排序的目的。

另外,Java 8 引入了并行排序算法(直接使用 parallelSort 方法),这是为了充分利用现代多核处理器的计算能力,底层实现基于 fork-join 框架,当处理的数据集比较小的时候,差距不明显,甚至还表现差一点;但是,当数据集增长到数万或百万以上时,提高就非常大了,具体还是取决于处理器和系统环境。

排序算法仍然在不断改进,最近双轴快速排序实现的作者提交了一个更进一步的改进,历时多年的研究,目前正在审核和验证阶段。根据作者的性能测试对比,相比于基于归并排序的实现,新改进可以提高随机数据排序速度提高 10%~20%,甚至在其他特征的数据集上也有几倍的提高。

3.常见的排序算法

排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。通常我们指的排序是内部排序。内部排序有很多,从大的层面可以分为两类,一类是比较排序(冒泡排序,选择排序,插入排序,归并排序,堆排序,快速排序……),一类是非比较排序(计数排序,基数排序,桶排序……)。

各类排序算法的性能对比:

输入图片说明

排序算法还有一个稳定性的概念:如果Ai = Aj,排序前Ai在Aj之前,排序后Ai还在Aj之前,则称这种排序算法是稳定的。通俗地讲就是保证排序前后两个相等的数的相对顺序不变。需要注意的是,排序算法是否为稳定的是由具体算法决定的,不稳定的算法在某种条件下可以变为稳定的算法,而稳定的算法在某种条件下也可以变为不稳定的算法。冒泡排序就可以实现为稳定的,也可以实现为不稳定的。稳定性的意义在于,如果一个对象有两个以上的属性,先按第一个属性排序了,再按第二个属性排序,如果不是稳定的,第二次排序就可能会打乱按第一个属性排序的结果。

本文只详细介绍四种在Java集合框架中用到了的排序,在Java中的实现都有优化,优化的细节太多,所以本文试图从基本的原理出发帮助大家理解Java集合框架中的复杂算法:

3.1 快速排序 3.2 二分插入排序 3.3 堆排序 3.4 归并排序

猜你喜欢

转载自my.oschina.net/leaforbook/blog/1821943