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

近几天在处理的一个项目,需要频繁对一些有序超大集合进行目标查找,二分查找算法是这类问题的最优解。但是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-10-21 11:55:30

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

可查找重复元素的二分查找算法

可查找重复元素的二分查找算法 二分查找算法思想:又称为 折半查找,二分查找适合对已经排序好的数据集合进行查找.假设有一升序的数据集合,先找出升序集合中最中间的元素,将数据集合划分为两个子集,将最中间的元素和关键字key进行比较,如果等于key则返回:如果大于关键字key,则在前一个数据集合中查找:否则在后一个子集中查找,直到找到为止:如果没找到则返回-1. 思路: 1.先定义两个下标 , left = 0 , right = arr.length -1; 2.因为我们也不知道要循环多少次,定义一

算法学习一~分治法~二分查找,快速的找~

现在编程也算是走上门了,但是没有把算法掌握下来只能说自己还是门外汉,所以以后我们就开始努力的学习算法,现在把自己每天的学习分享在这里希望大家能喜欢,并且我也要在这里整理我一天的学习和思路,. 二分查找..大家经常需要在一个数组中寻找某个值.如果是一个已经拍好序的话那么可以很快的找到.我们考虑下暴力查找,就是a[n],中找一个m,那么最好时是o(1),最差时是0(n),最终平均情况就是长度n/2.这样的时间复杂度不算很高但是可以改进到logn的级别. 1 #include<stdio.h>//算

JDK自带的二分查找算法和自己写的普通二分查找算法的比较(java二分查找源代码)

一.描述 解析和比较JDK自带的二分查找算法和自己写的普通二分查找算法,使用二进制位无符号右移来代替除2运算,并使用产生随机数的方法产生一定范围的随机数数组,调用Arrays类的sort()静态方法,对int类型数组进行排序. Math.random()的用法:会产生一个[0,1)之间的随机数(注意能取到0,不能取到1),这个随机数的是double类型,要想返回指定范围的随机数如[m,n]之间的整数的公式:(int)(Math.random()*(m-n+1)+m) 二.源代码 <span st

二分查找、二分查找大于等于key的第一个元素、二分查找小于等于key的最后一个元素

二分查找很简单,二分查找的变形需要注意一些细节. 1.当找大于等于key的第一个元素,或者查找小于等于key的最后一个元素时, 循环条件是 low < high,这和基本的二分查找不同, 但需要在循环退出的时候,判断是否满足条件: 2.如果是找最后一个满足条件的情况, 下限移动时不能用 low=mid+1:而应该用 low=mid: 此时,mid计算时应该用 mid=(low+high+1)/2, 保证 最后low.high相差1时不会陷入死循环, 循环退出后,下限可能是结果: 3.如果是找第一

算法回顾(三) 二分查找

二分查找,顾名思义,它的原理是,将排序好的数列分成两部分,判断期待值在高位部分还是在低位部分,然后再将期待值所在的那个区间的数列重新按照这个规则划分成两部分,再比较,直到最后不能划分为止. 优点是比较次数少,查找速度快,平均性能好:其缺点是要求待查表为有序表,且插入删除困难.因此,折半查找方法适用于不经常变动而查找频繁的有序列表. 假设其数组长度为n,那么该算法复杂度为o(log(n)).代码如下: 1 int binarySearch( int *a , int begin, int end,

集训第四周(高效算法设计)B题 (二分查找优化题)

---恢复内容开始--- Description Before the invention of book-printing, it was very hard to make a copy of a book. All the contents had to be re-written by hand by so calledscribers. The scriber had been given a book and after several months he finished its

Bailian4134 查找最接近的元素【二分查找】

4134:查找最接近的元素 总时间限制: 1000ms 内存限制: 65536kB 描述 在一个非降序列中,查找与给定值最接近的元素. 输入 第一行包含一个整数n,为非降序列长度.1 <= n <= 100000. 第二行包含n个整数,为非降序列各元素.所有元素的大小均在0-1,000,000,000之间. 第三行包含一个整数m,为要询问的给定值个数.1 <= m <= 10000. 接下来m行,每行一个整数,为要询问最接近元素的给定值.所有给定值的大小均在0-1,000,000,

集训第四周(高效算法设计)N题 (二分查找优化题)

原题:poj3061 题意:给你一个数s,再给出一个数组,要求你从中选出m个连续的数,m越小越好,且这m个数之和不小于s 这是一个二分查找优化题,那么区间是什么呢?当然是从1到数组长度了.比如数组长度为10,你先找5,去枚举每一个区间为5的连续的数,发现存在这样的数,那么就可以继续往左找,反之则往右找,直到左右区间重合,即为正确答案,(有可能是右区间小于左区间,所以每次都应该求区间中点值) #include"iostream" #include"set" #incl

【二分查找】无重复数组的二分查找(非递归版本)

// 二分查找法 #include <iostream> #include <vector> using namespace std; int BinarySearch(vector<int> vec,int target) { // 特殊输入 if(vec.size() <= 0) return -1; // 二分查找(非递归) int low = 0; int mid = 0; int high = vec.size()-1; // 注意:取等号 while(