排序集合和二分查找
1.有序集合
二分查找(也称折半查找)是非常重要和常见的查找算法,在平衡树和排序集合中应用较多。使用二分查找是有前提条件的,首先要保证集合时一个排序集合,只有在排序集合中才能使用二分查找。
在各种类型的数据中,一些数据天生就是有顺序的。比如数字,它天生就拥有自然排序的属性。还有一些数据类型,比如字符串,它也有约定俗成的排序规则。在Java语言中,判断一个类型的数据是否具有可排序的属性,通常看它本身是否实现了Comparable接口(比较器接口)。如果自定义的数据类型中实现了Comparable接口,它也是具有自然排序的属性。
Comparable接口是一个泛型接口,实现该接口需要实现"int compareTo(T o)"抽象方法,它的重写规则是:如果当前对象的值比o小,该方法返回一个负整数,否则返回一个正整数,如果两个值相等返回0。
在一些工具类容器中,为了避免出现泛型接口劫持的情况,允许向容器内添加非Comparable实现的类型,但必须额外提供一个外比较器Comparator对象,外比较器中的方法"int compare(T o1, T o2)"中定义了o1和o2的比较规则。这两种做法都可以保证自定义类型拥有排序属性。
2.二分查找
二分查找的思想非常简单,在一个已排序序列的一半处开始进行查找,根据比较的大小情况再决定向左还是向右继续进行查找。在使用二分查找算法时,通常需要定义三个比较索引,用于查找过程中对比较范围的约束:
1.left索引,在序列的最小元素处。
2.right索引,在序列的最大元素处。
3.middle索引,在序列的中间位置。
查找的目标值target总是与middle进行比较,当target大于middle时,left索引移动到middle+1处,随后middle索引移动到(left+right)/2处。
如果target小于middle时,right索引移动到middle-1处,随后随后middle索引移动到(left+right)/2处。直到left的索引大于right时,查找结束,如果查找结束时,依然没有找到有序集合中的target值,说明target值不在集合中(如下图所示)。
二分查找算法
当我们要在Java中要设计一个排序集合时,就可以使用到二分查找的算法。我们无需从头设计一个全新的线性集合,可以在ArrayList的基础上进行改造,将其改造成排序集合,并增加二分查找方法。由于ArrayList本身不是排序集合,我们需要覆盖它的add方法,让进入容器的数据保持有序性(示例代码如下)。
改造完的SortList是变成了一个自然排序集合。我们可以随机生成一些数据,测试一下改造的SortList是否能够自动对元素进行排序,以及新添加的二分查找方法能否找到目标数据。
运行结果:
通过上述示例的运行结果,可以验证我们改造的排序集合是有效的(可自行增加功能测试用例,验证代码的健壮性)。同时,我们也可以自定义一些类,来查看我们改造的SortList是否有效。在下面示例中,我们创建了学生成绩类TestScores,该类定义比较规则。在测试中,将多个学生成绩放入SortList中,看是否可以达到自动排序的效果。
运行结果:
通过运行结果来看,达到了我们的预期效果。并且可以利用findInsertIndex方法实现一些统计效果。
虽然在Java中存在类似的排序集合(TreeMap、TreeSet等),但是对于二分查找这样的基础算法,我们还是需要掌握的。