09. 算法之二分查找

前言

二分查找是数据结构中很常见的算法。在日常生活中也随处可见,比如,选一个1到100 的数字,让对方猜数字。那么比较快速的方式就是折半猜测。

1. 概念

二分查找(Binary Search)算法,也叫折半查找算法。当我们要从一个序列中查找一个元素的时候,二分查找是一种非常快速的查找算法。二分查找是针对有序数据集合的查找算法,如果是无序数据集合二分查找不会有效率的提升。

2. 本质

二分查找之所以快速,是因为它在匹配不成功的时候,每次都能排除剩余元素中一半的元素。因此可能包含目标元素的有效范围就收缩得很快,而不像顺序查找那样,每次仅能排除一个元素。

3. 小例子

package org.wanlong.halfSearch;

/**
 * @author wanlong
 * @version 1.0
 * @description: 二分查找实现类
 * @date 2023/5/30 14:06
 */
public class HalfSearch {


    /**
     * @param nums: 待查找数组
     * @param t:    待查找目标元素
     * @return int
     * @Description: 查找方法
     * @Author: wanlong
     * @Date: 2023/5/30 14:07
     **/
    public static int binarySearch(int[] nums, int t) {

        int low = 0;
        int high = nums.length - 1;
        int mid = 0;

        while (low <= high) {
            //找到中间下标
            mid = (low + high) / 2;
            //如果相等,说明已经找到了
            if (nums[mid] == t) {
                return mid;
            } else if (nums[mid] > t) {
                //中间位置 大于 目标值,说明目标值在左半部分
                high = mid - 1;
            } else {
                low = mid + 1;
            }
        }
        //如果都没找到,返回-1
        return -1;
    }

    /**
     * @param nums:
     * @param t:
     * @return int
     * @Description:递归二分查找
     * @Author: wanlong
     * @Date: 2023/5/30 14:16
     **/
    public static int binarySearchReCursion(int[] nums, int t, int low, int high) {

        //结束条件
        if (low > high) {
            return -1;
        }

        int mid = (low + high) / 2;
        if (nums[mid] == t) {
            return mid;
        } else if (nums[mid] < t) {
            low = mid + 1;
            return binarySearchReCursion(nums, t, low, high);
        } else {
            high = mid - 1;
            return binarySearchReCursion(nums, t, low, high);
        }
    }
}

测试类:

public class Clint {

    @Test
    public void testBin(){

        int[] ints = {1, 2, 3, 4,6,7,8,9,56,4545};

        int i = HalfSearch.binarySearch(ints, 4);
        System.out.println(i);
    }

    @Test
    public void testBinRec(){

        int[] ints = {1, 2, 3, 4,6,7,8,9,56,4545};

        int i = HalfSearch.binarySearchReCursion(ints, 4,0,ints.length-1);
        System.out.println(i);
    }
}

4. 经典案例

4.1 问题描述

一个有序数组有一个数出现1次,其他数出现2次,找出出现一次的数
比如:1 1 2 2 3 3 4 4 5 5 6 6 7 出现1次的数是7
要求算法的时间复杂度为 O(logn)

4.2 问题分析

  1. 如果暴力破解的话,遍历数组,一定可以找到出现一次的元素,但是此时的时间复杂度是O(n)
  2. 当前数据有两个特征: 1. 有序; 2. 时间复杂度 O(logn) ,为此,考虑看二分查找是否可行。
  3. 假设只出现一次的元素位于下标x。,由于其余每个元素都出现两次,因此下标 的左边和右边都有偶数个元素,数组的长度是奇数。
    由于数组是有序的,因此数组中相同的元素一定相邻。
  4. 对于下标x左边的下标y,如果 nums(y) = nums(y+1),则y一定是偶数;
  5. 对于下标x右边的下标z,如果 nums(z) = nums(z+1),则一定是奇数。
  6. 由于下标x 是相同元素的开始下标的奇偶性的分界,因此可以使用二分查找的方法寻找下标。初始时,二分查找的左边界是 0,右边界是数组的最大下标,每次取左右边界的平均值 mid 作为待判断的下标,根据 mid 的奇偶性决定和左边或右边的相邻元素比较:
  7. 如果 mid 是偶数,则比较 nums(mid) 和 nums(mid+1)是否相等
  8. 如果 mid是奇数,则比较 nums(mid -1)和 nums(mid)是否相等
    如果上述比较相邻元素的结果是相等,则 mid< x,调整左边界,否则 mid>=x,调整右边界。调整边界之后继续二分查找,直到确定下标的值,得到下标x的值之后,nums[x] 即为只出现一次的元素
  9. 利用按位异或的性质,可以得到 mid 和相邻的数之间的如下关系,其中 是按位异或运算符
    . 当 mid 是偶数时,mid +1= mid异或1;
    . 当 mid 是奇数时,mid -1 = mid异或1。
    因此在二分查找的过程中,不需要判断 mid 的奇偶性,mid和 mid异或1 即为每次需要比较元素的两个下标

4.3 代码验证

class Solution {
    public int singleNonDuplicate(int[] nums) {
        int low = 0, high = nums.length - 1;
        while (low < high) {
            int mid = (high - low) / 2 + low;
            if (nums[mid] == nums[mid ^ 1]) {
                low = mid + 1;
            } else {
                high = mid;
            }
        }
        return nums[low];
    }
}

5. 复杂度

时间复杂度是 O(logn)
空间复杂度O(1)

6. 优缺点

6.1 优点

速度快,不占空间,不开辟新空间

6.2 缺点

必须是有序的数组,数据量太小没有意义,但数据量也不能太大,因为数组要占用连续的空间

7. 应用

有序的查找都可以使用二分法。
如何快速定位出一个 IP 地址的归属地?
如果 IP 区间与归属地的对应关系不经常更新,我们可以先预处理这 12 万条数据,让其按照起始 IP 从小到大排序。如何来排序呢?我们知道,IP 地址可以转化为 32 位的整型数。所以,我们可以将起始地址,按照对应的整型值的大小关系,从小到大进行排序。当我们要查询某个 IP 归属地时,我们可以先通过二分查找,找到最后一个起始 IP 小于等于这个 IP 的IP 区间,然后,检查这个 IP 是否在这个 IP 区间内,如果在,我们就取出对应的归属地显示;如果不在,就返回未查找到

以上,本人菜鸟一枚,如有错误,请不吝指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值