背景

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

    介绍

  • 二分查找针对的是一个有序的数据集合,查找思想有点类似分治思想。每次都通过跟区间的中间元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为 0。

  • 二分查找是时间复杂度为 O(logn) 的算法

    应用场景(局限性)

  • 首先,二分查找依赖的是顺序表结构,简单点说就是数组。

  • 其次,二分查找针对的是有序数据。
  • 再次,数据量太小不适合二分查找。

    代码实现(简单二分法查找)

    有序数组中不存在重复元素

    ```javascript // 循环实现 const bsearch = (arr = [], value) => { const len = arr.length if (len === 0) return -1 let low = 0 let height = len - 1 while (low <= height) { let mid = Math.floor((height + low) / 2) if (value === arr[mid]) {
    1. return mid
    } else if (value < arr[mid]) {
    1. // 在左半区间
    2. height = mid - 1
    } else {
    1. // 在右半区间
    2. low = mid + 1
    } } return -1 }

// 递归实现 const len = arr.length if (len === 0) return -1 return bsearchInter(arr, 0, len - 1, value) }

const bsearchInter = (arr, low, height, value) => { if(low > height) return -1 let mid = Math.floor((height + low) / 2) if (arr[mid] === value) { return mid } else if (value < arr[mid]) { return bsearchInter(arr, low, mid -1, value) } else { return bsearchInter(arr, mid + 1, height -1, value) } }

  1. - 循环退出条件注意是 low<=high,而不是 low<high
  2. - mid 的取值实际上,mid=(low+high)/2 这种写法是有问题的。因为如果 low high 比较大的话,两者之和就有可能会溢出。改进的方法是将 mid 的计算方式写成 low+(high-low)/2。更进一步,如果要将性能优化到极致的话,我们可以将这里的除以 2 操作转化成位运算 low+((high-low)>>1)。因为相比除法运算来说,计算机处理位运算要快得多。
  3. - low high 的更新low=mid+1high=mid-1。注意这里的 +1 -1,如果直接写成 low=mid 或者 high=mid,就可能会发生死循环。比如,当 high=3low=3 时,如果 a[3]不等于 value,就会导致一直循环不退出。
  4. <a name="A1XdV"></a>
  5. #### 题目
  6. - 如何编程实现“求一个数的平方根”?要求精确到小数点后 6 位。
  7. <a name="eKYeC"></a>
  8. ### 二分法查找变形问题
  9. <a name="lHmrk"></a>
  10. ## ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12492651/1624199217340-3acc2328-6516-4e61-923b-e51e3a4004b5.png#clientId=u1324d288-2ad6-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=357&id=ufa91e5ee&margin=%5Bobject%20Object%5D&name=image.png&originHeight=714&originWidth=1076&originalType=binary&ratio=2&rotation=0&showTitle=false&size=720476&status=done&style=none&taskId=ud5bf7d70-3f9c-44f6-8adc-84b22319c97&title=&width=538)
  11. <a name="j0rr5"></a>
  12. #### 查找第一个值等于给定值的元素
  13. - 有序数据集合中存在重复的数据,我们希望找到第一个值等于给定值的数据
  14. - 比如下面这样一个有序数组,其中,a[5],a[6],a[7]的值都等于 8,是重复的数据。我们希望查找第一个等于 8 的数据,也就是下标是 5 的元素。
  15. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12492651/1624199442648-ff3ba752-ac95-4b5e-9408-4052cb6060d0.png#clientId=u1324d288-2ad6-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=134&id=u5e6a8a1f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=268&originWidth=1088&originalType=binary&ratio=2&rotation=0&showTitle=false&size=137211&status=done&style=none&taskId=u5abaf958-a591-4acf-8236-575a424b904&title=&width=544)
  16. ```javascript
  17. const arr = [1,2,3,3,3,3,4]
  18. const bsearch = (arr = [], value) => {
  19. const len = arr.length
  20. if (len === 0) return -1
  21. let low = 0
  22. let height = len - 1
  23. while (low <= height) {
  24. let mid = Math.floor((height + low) / 2)
  25. if (arr[mid] < value) {
  26. // 右区间
  27. low = mid + 1
  28. } else if (arr[mid] > value) {
  29. // 左区间
  30. height = mid - 1
  31. } else {
  32. // 这里肯定找到元素了,判断它前一个元素值和给定值 value 是否相等即可
  33. // 第一个元素 mid === 0
  34. // 前一个元素值和给定值 value 是否相等
  35. if (mid === 0 || arr[mid - 1] !== value) {
  36. return mid
  37. }
  38. // 如果 前一个元素 arr[mid - 1 === value
  39. // 再次 二分
  40. height = mid - 1
  41. }
  42. }
  43. return -1
  44. }

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

  1. const arr = [1,2,3,3,3,3,4]
  2. const bsearch = (arr = [], value) => {
  3. const len = arr.length
  4. if (len === 0) return -1
  5. let low = 0
  6. let height = len - 1
  7. while (low <= height) {
  8. let mid = Math.floor((height + low) / 2)
  9. if (arr[mid] < value) {
  10. // 右区间
  11. low = mid + 1
  12. } else if (arr[mid] > value) {
  13. // 左区间
  14. height = mid - 1
  15. } else {
  16. // 这里肯定找到元素了,判断它后一个元素值和给定值 value 是否相等即可
  17. // 第一个元素 mid === 0
  18. // 前一个元素值和给定值 value 是否相等
  19. if (arr[mid + 1] !== value) {
  20. return mid
  21. }
  22. // 如果 后一个元素 arr[mid + 1] === value
  23. // 再次 二分
  24. low = mid + 1
  25. }
  26. }
  27. return -1
  28. }

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

  1. const bsearch = (arr = [], value) => {
  2. const len = arr.length
  3. if (len === 0) return -1
  4. let low = 0
  5. let height = len - 1
  6. while (low <= height) {
  7. let mid = Math.floor((height + low) / 2)
  8. if (arr[mid] >= value) {
  9. // 判断是不是第一个元素就好
  10. if((mid === 0) || arr[mid - 1] < value) {
  11. return mid
  12. } else {
  13. height = mid - 1
  14. }
  15. } else {
  16. low = mid + 1
  17. }
  18. }
  19. return -1
  20. }

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

  1. const bsearch = (arr = [], value) => {
  2. const len = arr.length
  3. if (len === 0) return -1
  4. let low = 0
  5. let height = len - 1
  6. while (low <= height) {
  7. let mid = Math.floor((height + low) / 2)
  8. if (arr[mid] > value) {
  9. height = mid - 1
  10. } else { // 小于等于 情况
  11. // 判断后一个元素是否大于 value
  12. // mid === len 最后一个元素
  13. if (mid == len || arr[mid + 1] > value) {
  14. return mid
  15. } else {
  16. low = mid + 1
  17. }
  18. }
  19. }
  20. return -1
  21. }

题目

—假设我们有 12 万条这样的 IP 区间与归属地的对应关系,如何快速定位出一个 IP 地址的归属地呢?

  • 将12万条32位IP地址转为整型
  • 让其按照起始 IP 从小到大排序
  • 查找最后一个小于等于给定值的元素

比如:假设坐标区间是0-5 10-15 20-25 30-35 查找的坐标是22,

  1. 将坐标区间起始数字组成数组 (0 10 20 30)
  2. 查找最后一个小于22的起始区间20 看22在不在20-25的区间内, 在的话返回地址,不在返回没有查询到

代码实现

  • 32位IP地址转为整型

>> 位操作
32位IP地址转为整型
正则验证IP

  • 2(5[0-5]|[0-4]\d) 匹配:200 ~ 255
  • [0-1]?\d{1,2} 匹配:0 ~ 199
  • 0 到 255 的式子已经写出来了,那么一共四段再加上中间的点就很容易了
    1. 后边“点”和“数字”重复三次就可以了
    2. (\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}
    1. // 利用位操作
    2. // 注意: parseInt(result[4]))>>>0;无符号右移0位
    3. function ipToInt(IP){
    4. const REG =/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/;
    5. const reg = /[0-1]?\d{1,2}|2([0-4]\d|5[0-5])(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}/
    6. const pattern = /((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}/g,
    7. var xH = "",result = reg.exec(IP);
    8. if(!result) return -1;
    9. return (parseInt(result[1]) << 24
    10. | parseInt(result[2]) << 16
    11. | parseInt(result[3]) << 8
    12. | parseInt(result[4]))>>>0;
    13. }