算法:支持重复元素的二分查找

近几天在处理的一个项目,需要频繁对一些有序超大集合进行目标查找,二分查找算法是这类问题的最优解。但是java的Arrays.binarySearch()方法,如果集合中有重复元素,而且遇到目标元素正好是这些重复元素之一,该方法只能返回一个,并不能将所有的重复目标元素都返回,没办法,只能自造轮子了。

先复习下二分查找的经典算法:

 1     private int binarySearch1(Integer[] A, Integer x) {
 2         int low = 0, high = A.length - 1;
 3         while (low <= high) {
 4             int mid = (low + high) / 2;
 5             if (A[mid].equals(x)) {
 6                 return mid;
 7             } else if (x < A[mid]) {
 8                 high = mid - 1;
 9             } else {
10                 low = mid + 1;
11             }
12         }
13         return -1;
14     }

思路很简单,先定位到中间元素,如果中间元素比目标元素大,则扔掉后一半,反之扔掉前一半,如果正好一次命中,直接返回。

略做改进:

 1     private List<Integer> binarySearch2(Integer[] A, Integer x) {
 2         List<Integer> result = new ArrayList<Integer>();
 3         int low = 0, high = A.length - 1;
 4         while (low <= high) {
 5             int mid = (low + high) / 2;
 6             if (A[mid].equals(x)) {
 7                 if (mid > 0) {
 8                     //看前一个元素是否=目标元素
 9                     if (A[mid - 1].equals(x)) {
10                         for (int i = mid - 1; i >= 0; i--) {
11                             if (A[i].equals(x)) {
12                                 result.add(i);
13                             } else break;
14                         }
15                     }
16                 }
17                 result.add(x);
18                 if (mid < high) {
19                     //看后一个元素是否=目标元素
20                     if (A[mid + 1].equals(x)) {
21                         for (int i = mid + 1; i <= high; i++) {
22                             if (A[i].equals(x)) {
23                                 result.add(i);
24                             } else break;
25                         }
26                     }
27                 }
28                 return result;
29             } else if (x < A[mid]) {
30                 high = mid - 1;
31             } else {
32                 low = mid + 1;
33             }
34         }
35         return result;
36
37     }

思路:命中目标后,看下前一个紧挨着的元素是否也是要找的元素,如果是,则顺着向前取,直到遇到不等于目标元素为止。然后再看后一个紧挨着的元素,做类似处理。

测试:

1         Integer[] A = new Integer[]{1, 2, 3, 4, 5, 5, 5, 6, 7, 8, 9};
2
3         System.out.println("binarySearch1 => ");
4         System.out.println(binarySearch1(A, 5));
5
6         System.out.println("binarySearch2 => ");
7         System.out.println(binarySearch2(A, 5));

binarySearch1 =>
5
binarySearch2 =>
[4, 5, 6]

从返回的下标值看,都在预期之中,但是事情并未到此止步,通常要查找的列表元素,并不是数值这么简单,一般是一些复杂的对象实例,为了做到通用,得弄成一个泛型版本:

 1    private <T> List<Integer> binarySearch4(List<T> A, T x, Comparator<? super T> comparator) {
 2         List<Integer> result = new ArrayList<Integer>();
 3         int low = 0, high = A.size() - 1;
 4         while (low <= high) {
 5             int mid = (low + high) / 2;
 6             int temp = comparator.compare(x, A.get(mid));
 7             if (temp == 0) {
 8                 if (mid > 0) {
 9                     if (comparator.compare(x, A.get(mid - 1)) == 0) {
10                         for (int i = mid - 1; i >= 0; i--) {
11                             if (comparator.compare(A.get(i), x) == 0) {
12                                 result.add(i);
13                             } else break;
14                         }
15                     }
16                 }
17                 result.add(mid);
18                 if (mid < high) {
19                     if (comparator.compare(x, A.get(mid + 1)) == 0) {
20                         for (int i = mid + 1; i <= high; i++) {
21                             if (comparator.compare(x, A.get(i)) == 0) {
22                                 result.add(i);
23                             } else break;
24                         }
25                     }
26                 }
27                 return result;
28
29             } else if (temp < 0) {
30                 high = mid - 1;
31             } else {
32                 low = mid + 1;
33             }
34         }
35
36         return result;
37     }

为了比较二个复杂对象实例的大小,引入了Comparator接口,可以根据业务需要,则调用者自定义比较规则。

测试一下:

先定义一个业务对象类AwbDto:

 1 package com.cnblogs.yjmyzz.test.domain;
 2
 3 /**
 4  * Created by jimmy on 15/1/8.
 5  */
 6 public class AwbDto {
 7
 8
 9     /**
10      * 运单号
11      */
12     private String awbNumber;
13
14     /**
15      * 始发站
16      */
17     private String originAirport;
18
19     public AwbDto(String awbNumber, String originAirport) {
20         this.awbNumber = awbNumber;
21         this.originAirport = originAirport;
22     }
23
24     public String getAwbNumber() {
25         return awbNumber;
26     }
27
28     public void setAwbNumber(String awbNumber) {
29         this.awbNumber = awbNumber;
30     }
31
32     public String getOriginAirport() {
33         return originAirport;
34     }
35
36     public void setOriginAirport(String originAirport) {
37         this.originAirport = originAirport;
38     }
39 }

还需要定义AwbData比较大小的业务规则,假设:只要运单号相同,则认为相等(即:用运单号来区分对象大小)

1     private class AwbDtoComparator implements Comparator<AwbDto> {
2
3         @Override
4         public int compare(AwbDto x, AwbDto y) {
5             return x.getAwbNumber().compareTo(y.getAwbNumber());
6         }
7     }

测试代码:

 1         List<AwbDto> awbList = new ArrayList<AwbDto>();
 2         awbList.add(new AwbDto("112-10010011", "北京"));
 3         awbList.add(new AwbDto("112-10010022", "上海"));
 4         awbList.add(new AwbDto("112-10010033", "天津"));
 5         awbList.add(new AwbDto("112-10010044", "武汉"));
 6         awbList.add(new AwbDto("112-10010044", "武汉"));
 7         awbList.add(new AwbDto("112-10010055", "广州"));
 8
 9         AwbDtoComparator comparator = new AwbDtoComparator();
10
11         AwbDto x = new AwbDto("112-10010044", "武汉");
12
13
14         System.out.println("binarySearch4 => ");
15         System.out.println(binarySearch4(awbList, x, comparator));

binarySearch4 =>
[3, 4]

测试结果,一切顺利,皆大欢喜,可以去休息了。

时间: 2024-08-01 02:35:37

算法:支持重复元素的二分查找的相关文章

《算法基础:打开算法之门》一3.1 二分查找

3.1 二分查找 在学习一些排序算法之前,首先学习二分查找,其中待查找的数组事先需要是有序的.二分查找的优点是从包含n个元素的数组中执行查找操作仅仅需要O(lgn)时间. 在书架那个例子中,当书架上的书已经按照作者名字从左向右依次排好序后才开始进行查找.我们将使用作者名字作为主关键字,现在让我们搜索下由Jonathan Swift所写的书.你可能已经推测到:因为作者的姓以"S"开头,"S"是字母表中的第19个字母,且19/26与3/4接近,因此你可能会浏览书架上位置

C语言二分查找算法及实现代码_C 语言

二分査找也称折半査找,其优点是查找速度快,缺点是要求所要査找的数据必须是有序序列.该算法的基本思想是将所要査找的序列的中间位置的数据与所要査找的元素进行比较,如果相等,则表示査找成功,否则将以该位置为基准将所要査找的序列分为左右两部分.接下来根据所要査找序列的升降序规律及中间元素与所查找元素的大小关系,来选择所要査找元素可能存在的那部分序列,对其采用同样的方法进行査找,直至能够确定所要查找的元素是否存在,具体的使用方法可通过下面的代码具体了解. #include <stdio.h> binar

Java实现二分查找算法实例分析_java

本文实例讲述了Java实现二分查找算法.分享给大家供大家参考.具体如下: 1. 前提:二分查找的前提是需要查找的数组必须是已排序的,我们这里的实现默认为升序 2. 原理:将数组分为三部分,依次是中值(所谓的中值就是数组中间位置的那个值)前,中值,中值后:将要查找的值和数组的中值进行比较,若小于中值则在中值前面找,若大于中值则在中值后面找,等于中值时直接返回.然后依次是一个递归过程,将前半部分或者后半部分继续分解为三部分.可能描述得不是很清楚,若是不理解可以去网上找.从描述上就可以看出这个算法适合

在MySQL中实现二分查找的详细教程_Mysql

给定一个升序排列的自然数数组,数组中包含重复数字,例如:[1,2,2,3,4,4,4,5,6,7,7].问题:给定任意自然数,对数组进行二分查找,返回数组正确的位置,给出函数实现.注:连续相同的数字,返回第一个匹配位置还是最后一个匹配位置,由函数传入参数决定. 我为什么会出这道题目?     二分查找在数据库内核实现中非常重要     在数据库的内核实现中,二分查找是一个非常重要的逻辑,几乎99%以上的SQL语句(所有索引上的范围扫描/等值查询/Unique查询等),都会使用到二分查找进行数据的

js基本算法:冒泡排序,二分查找的简单实例_基础知识

知识扩充: 时间复杂度:算法的时间复杂度是一个函数,描述了算法的运行时间.时间复杂度越低,效率越高. 自我理解:一个算法,运行了几次时间复杂度就为多少,如运行了n次,则时间复杂度为O(n). 1.冒泡排序 解析:1.比较相邻的两个元素,如果前一个比后一个大,则交换位置. 2.第一轮的时候最后一个元素应该是最大的一个. 3.按照步骤一的方法进行相邻两个元素的比较,这个时候由于最后一个元素已经是最大的了,所以最后一个元素不用比较. function sort(elements){ for(var i

php 二分查找法算法详解

一.概念:二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好:其缺点是要求待查表为有序表,且插入删除困难.因此,折半查找方法适用于不经常变动而查找频繁的有序列表.首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功:否则利用中间位置记录将表分成前.后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表.重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功. 二.代

二分查找算法在C/C++程序中的应用示例_C 语言

 二分查找算法的思想很简单,<编程珠玑>中的描述: 在一个包含t的数组内,二分查找通过对范围的跟综来解决问题.开始时,范围就是整个数组.通过将范围中间的元素与t比较并丢弃一半范围,范围就被缩小.这个过程一直持续,直到在t被发现,或者那个能够包含t的范围已成为空.         Donald Knuth在他的<Sorting and Searching>一书中指出,尽管第一个二分查找算法早在1946年就被发表,但第一个没有bug的二分查找算法却是在12年后才被发表出来.其中常见的一

JavaScript使用二分查找算法在数组中查找数据的方法_javascript技巧

本文实例讲述了JavaScript使用二分查找算法在数组中查找数据的方法.分享给大家供大家参考.具体分析如下: 二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好:其缺点是要求待查表为有序表,且插入删除困难.因此,折半查找方法适用于不经常变动而查找频繁的有序列表.首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功:否则利用中间位置记录将表分成前.后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一

[算法]二分查找算法

[思想] 二分搜索主要解决的问题是确定排序后的数组x[0,n-1]中是否包含目标元素target. 二分搜索通过持续跟踪数组中包含元素target的范围(如果target存在数组中的话)来解决问题. 一开始,这个范围是整个数组,然后通过将target与数组中的中间项进行比较并抛弃一半的范围来缩小范围.该过程持续进行, 直到在数组中找到target或确定包含target的范围为空时为止.在有n个元素的表中,二分搜索大约需要执行lgn次比较操作. 提供充足的时间,竟然只有10%的专业程序员能够将这个