二分法查找

15 篇文章 0 订阅
13 篇文章 0 订阅

如何用最省内存的方式实现快速查找功能

二分法思想

二分法查找针对的是一个有序的数据集合,每次通过与区间的中间元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为0

二分查找非常高效,假设数据大小是n,每次查找后数据都会缩小为原来的一半,也就是会除以2,最坏情况下,直到查找区间被缩小为空,才停止

 

当n/2k = 1时,k是总共缩小的次数,而每一次缩小操作只涉及两个数据的大小比较,经过K次区间缩小的操作,时间复杂度就是O(k),n/2k = 1得到k=log2 n,故时间复杂度就是O(logn)

二分查找的递归与非递归实现

  1. 最简单的二分查找实现(有序数据集合中不存在重复的数据)

     public static int binarySearch(int[]A,int value) {
            int low = 0;
            int len = A.length;
            int high = len -1;
    ​
            while(low <= high) {
                int mid = (low + high)>>1;
                if(A[mid] == value)
                    return mid;
                else if (A[mid] < value)
                    low = mid +1;
                else
                    high = mid -1;
            }
            return -1;
        }
    1. 二分查找易错的地方

      1. 循环退出的条件

        注意是low <= high,而不是low<high.

      2. mid的取值

        如果写成 mid (low+high)/2是有问题,如果low和high比较大,两者的和可能会溢出改进的方案low+(high-low)/2,但是计算机位运算比除法快,故可改成low+((high-low)>>1)

  2. 二分法的递归实现

    /**
         * 二分法搜索的递归实现
         */
        public static int bsearch(int[] a,int n,int val) {
            return bsearchInternally(a,0,n-1,val);
        }
    ​
        private static int bsearchInternally(int[] a,int low,int high,int value) {
            if(low >high) 
                return -1;
            int mid = low +((high - low)>> 1);
            if(a[mid] == value) {
                return mid;
            }else if(a[mid] < value) 
                return bsearchInternally(a,mid+1,high,value);
            else {
                return bsearchInternally(a,low,mid-1,value);
            }
        }

    二分法查找应用场景

    1. 二分查找依赖顺序表结构

      二分查找不能依赖如链表的的其他结构,主要原因是二分查找算法需要按照下标随机访问元素,链表随机访问的时间复杂度是O(n),使用链表存储,二分查找的时间复杂度就会变得很高

    2. 二分查找针对的有序数组

      二分查找对数据要求必须是有序的,如果数据没有序,则需要先排序

    3. 数据量太小或数据量太大也不适合二分查找

思考题:

  1. 如何在100万个整数中快速找某个整数?

假设内存限制是100MB,每个数据大小是8字节,将数据存储在数组中,内存占用差不多是80MB,符合内存的限制,先对100万数据从小到大排序,然后再利用二分查找算法,可以快速地找到想要的数;

虽然散列表,二叉树这些支持快速查找的动态数据结构,也可以解决这个问题,实际上不行,因为用散列表或者二叉树来存储这100万的数据,100MB内存肯定是存不下的

 

如何快速定位IP对应的省份地址

实现一个根据IP地址来查找IP归属地的功能,通过维护一个很大的IP地址库来实现,当我们想要查询202.102.133.13这个IP地址的归属地时,在地址库中搜索,发现这个IP地址落在[202.102.133.0,202.102.133.255]这个地址范围内,则可以将这个IP地址范围对应的归属地山东东营市显示给用户

[202.102.133.0, 202.102.133.255] 山东东营市

[202.102.135.0, 202.102.136.255] 山东烟台

[202.102.156.34, 202.102.157.255] 山东青岛

[202.102.48.0, 202.102.48.255] 江苏宿迁

[202.102.49.15, 202.102.51.251] 江苏泰州

[202.102.56.0, 202.102.56.255] 江苏连云港

现在的问题是在庞大的地址库中逐一比对IP地址所在的区间,是非常耗时的,假设我们有12万条这样的IP区间与归属地对应关系,如何快速定位出一个IP地址的归属地呢?

二分搜索的变形问题

四种常见的二分查找变形问题

  • 查找第一个值等于给定值得元素;

     /**
         * 查找第一个等于value的值,实现方案一
         *
         * @param a
         * @param value
         * @return
         */
        public static int binarySearchFirstOne(int[] a, int value) {
            int n = a.length;
            int low = 0;
            int high = n - 1;
    ​
            while (low <= high) {
                int mid = low + ((high - low) >> 1);
                if (a[mid] >= value)
                    high = mid - 1;
                else
                    low = mid + 1;
            }
            if (low < n && a[low] == value) {
                return low;
            } else {
                return -1;
            }
        }
    ​
        /**
         * 查找第一个等于value的值,实现方案二
         *
         * @param a
         * @param value
         * @return
         */
        public static int binarySearchFirstTwo(int[] a, int value) {
            int n = a.length;
            int low = 0;
            int high = n - 1;
    ​
            while (low <= high) {
                int mid = low + ((high - low) >> 1);
                if (a[mid] > value)
                    high = mid - 1;
                else if (a[mid] < value)
                    low = mid + 1;
                else {
                    //当a[mid]=value时,若mid=0,则mid肯定是第一个元素,或者a[mid-1]!=value,则mid前面的元素都是小于value的,
                    // 因为数组是有序的,所以mid就是第一个等于value的值
                    if (mid == 0 || a[mid - 1] != value)
                        return mid;
                    else
                        high = mid - 1;
                }
            }
            return -1;
        }

     

  • 查找最后一个值等于给定值的元素;

    /**
         * 查找最后一个等于value得元素
         *
         * @param a
         * @param value
         * @return
         */
        public static int binarySearchLast(int[] a, int value) {
            int low = 0;
            int n = a.length;
            int high = n - 1;
            while (low <= high) {
                int mid = low + ((high - low) >> 1);
                if (a[mid] > value)
                    high = mid - 1;
                else if (a[mid] < value)
                    low = mid + 1;
                else {
                    //当a[mid]=value时,若mid=n-1,则mid肯定是第一个元素,或者a[mid+1]!=value,则mid后面的元素都是大于value的,
                    // 因为数组是有序的,所以mid就是最后一个等于value的值
                    if (mid == n - 1 || a[mid + 1] != value)
                        return mid;
                    else
                        low = mid + 1;
                }
            }
            return -1;
        }
    ​
    

     

  • 查找第一个大于等于给定值的元素;

    /**
         * 查找第一个大于等于给定值的元素
         * @param a
         * @param value
         * @return
         */
        public static int bindSearchFirstGreatOrEqual(int[] a, int value) {
            int n = a.length;
            int low = 0;
            int high = n - 1;
    ​
            while (low <= high) {
                int mid = low + ((high - low) >> 1);
                if (a[mid] >= value) {
                    if (mid == 0 || a[mid - 1] < value)
                        return mid;
                    else
                        high = mid - 1;
                } else {
                    low = mid + 1;
                }
            }
            return -1;
        }

     

  • 查找最后一个小于等于给定值的元素;

    /**
         * 查找最后一位小于等于value的元素
         * @param a
         * @param value
         * @return
         */
        public static int bindSearchLastLessOrEqual(int[] a, int value) {
            int n = a.length;
            int low = 0;
            int high = n - 1;
    ​
            while (low <= high) {
                int mid = low + ((high - low) >> 1);
                if (a[mid] > value) {
                    high = mid - 1;
                } else {
                    if(mid==n-1 || a[mid+1]>value){
                        return mid;
                    }else{
                        low = mid + 1;
                    }
    ​
                }
            }
            return -1;
        }

     

思考题:如何快速定位一个IP地址的归属地?

如果IP区间与归属地的对应关系不经常更新,则可预先处理这12万条数据,让其按照起始IP从小到大排序,IP地址可以转化为32位的整型数,故可以将起始地址,按照对应的整型值的大小关系,从小到大进行排序.然后这个问题就转化为在有序数组中,查找最后一个小于等于某个给定值的元素,当要查询某个IP归属地时,找到最后一个起始地址IP下于等于这个IP的IP区间,然后检查这个IP是否在这个IP区间内,如果在,就取出对应的归属地显示,反之显示未查找到

  • 15
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值