1-1二分查找

news2025/2/10 22:36:21

二分查找

  • 1 基础版
    • 1.1 算法描述
    • 1.2 算法流程图
    • 1.3 算法实现
      • 1.3.1 Java实现
  • 2 改动版
    • 2.1 算法描述
    • 2.2 算法流程图
    • 2.3 算法实现
      • 2.3.1 Java实现
    • 2.4 改进点分析
      • 2.4.1 区间定义差异
      • 2.4.2 核心改进原理
      • 2.4.3 数学等价性证明
  • 3 平衡版
    • 3.1 算法描述
    • 3.2 算法流程图
    • 3.3 算法实现
      • 3.3.1 Java实现
    • 3.4 改进点分析
      • 3.4.1 区间定义差异
      • 3.4.2 核心改进原理
      • 3.4.3 数学等价性证明
      • 3.4.4 性能分析
  • 4 对比总结
    • 4.1 区间定义
    • 4.2 中间索引计算
    • 4.3 终止条件
    • 4.4 改进点
    • 4.5 性能分析

二分查找Binary Search)是一种高效的搜索算法,适用于已经排好序的数组。它通过将待查找的元素与数组中间的元素进行比较,从而每次可以排除掉一半的元素,以此来快速缩小搜索范围,直到找到目标元素或确定其不存在于数组中。该算法的时间复杂度为 O ( l o g n ) O(logn) O(logn),意味着随着输入规模的增长,查找时间增长缓慢


1 基础版

需求:在有序数组 A A A中查找值 t a r g e t target target

  • 如果找到,则返回该值在数组中的索引。
  • 如果未找到,则返回 − 1 -1 1

1.1 算法描述

  1. 初始化:给定一个内含 n n n个元素的有序数组 A A A,满足 A 0 ≤ A 1 ≤ A 2 ≤ . . . ≤ A n − 1 A_{0} ≤ A_{1} ≤ A_{2} ≤ ... ≤ A_{n−1} A0A1A2...An1,一个待查值 t a r g e t target target
  2. 设置初始索引:设置 i = 0 i = 0 i=0 j = n − 1 j = n − 1 j=n1
  3. 终止条件:如果 i > j i > j i>j,结束查找,找不到需要的值,返回 − 1 −1 1
  4. 计算中间索引:设置 m = ⌊ i + j 2 ⌋ m = \lfloor \frac{i+j}{2} \rfloor m=2i+j m m m为数组的中间索引, ⌊ ⋅ ⌋ \lfloor ⋅ \rfloor 表示向下取整。
  5. 比较并调整索引
    • 如果 t a r g e t < A m target < A_{m} target<Am,设置 j = m − 1 j = m − 1 j=m1,跳转至第2步。
    • 如果 A m < t a r g e t A_{m} < target Am<target,设置 i = m + 1 i = m + 1 i=m+1,跳转至第2步。
    • 如果 A m = t a r g e t A_{m} = target Am=target,结束查找,并返回所在位置的数组索引 m m m

1.2 算法流程图

target < A[m]
A[m] < target
A[m] = target
开始
设置 i=0, j=n-1
检查 i <= j
返回 -1, 结束查找
计算 m = floor((i+j)/2)
比较 target 和 A[m]
设置 j = m - 1
设置 i = m + 1
返回 m, 结束查找

1.3 算法实现

1.3.1 Java实现

这是一种最简单的二分查找算法的实现,从逻辑上没什么问题,但是在实际应用的过程中是可能会出bug的。

/**
 * 使用二分查找算法在一个有序数组中查找目标值的基本实现
 *
 * @param arr    一个有序的整数数组
 * @param target 要查找的目标值
 * @return 目标值在数组中的索引,如果目标值不在数组中,则返回-1
 */
public static int binarySearchBasic(int[] arr, int target) {
    // TODO 1. 设置初始索引
    int left = 0; // 左边界
    int right = arr.length - 1; // 右边界

    // TODO 2. 循环查找
    while (left <= right) {
        // TODO 2.1. 计算中间索引
        int mid = (right + left) >>> 1;
        // TODO 2.2. 比较中间索引的值与目标值
        if (arr[mid] == target) {
            // TODO 2.3. 如果等于目标值,返回中间索引
            return mid;
        } else if (arr[mid] < target) {
            // TODO 2.4. 如果小于目标值,更新左边界
            left = mid + 1;
        } else {
            // TODO 2.5. 如果大于目标值,更新右边界
            right = mid - 1;
        }
    }

    // TODO 3. 如果循环结束,没有找到目标值,返回-1
    return -1;
}
  1. 循环条件为左边界≤右边界
    • 循环条件 left <= right 保证了以下两种情况:
      • 当区间仍有元素时left <= right),继续检查。
      • 当区间为空时left > right),终止循环。
    • 若循环条件为 left < right
      • 问题场景:当区间仅剩一个元素时(left == right),循环条件不成立,直接跳过检查。
      • 后果:若该元素恰好是目标值,算法会错误地返回 -1
    • 正确条件 left <= right
      • 覆盖所有有效区间:即使只剩一个元素(left == right),仍会进入循环检查。
      • 终止条件正确性:当 left > right 时,区间已为空,表明目标值不存在。
  2. 中间索引如何求
    • (right + left) / 2

      这一种是直接计算中间值的方法

      计算方式在数学上是正确的,但是这种方法存在一个严重的潜在问题整数溢出

      • leftright 都接近 int 类型的最大值(Integer.MAX_VALUE,即 2^31 - 1)时,left + right 可能会超过 int 类型的最大值,导致溢出
      • 溢出后,计算结果会变成负数,导致 mid 的值错误,进而引发索引越界或逻辑错误。
    • left + (right - left) / 2

      这一种是通过偏移量计算中间值的方法

      • 避免溢出right - left 的结果一定小于 right,不会超过 int 的范围。
      • 逻辑清晰:通过偏移量计算中间值,更直观地表达“从 left 开始,加上区间长度的一半”。
    • 使用 >>(有符号右移)

      >>有符号右移操作符,它会保留符号位(即最高位),并在左侧补上与符号位相同的位。

      int mid = (left + right) >> 1;
      
      • 特点
        • 等价于除以 2(left + right) >> 1 等价于 (left + right) / 2
        • 保留符号位:如果 left + right 是负数,右移后结果仍然是负数。
        • 性能优化:右移操作比除法操作更快,适合对性能要求较高的场景。
      • 潜在问题
        • 整数溢出:如果 left + right 超过 int 的最大值(Integer.MAX_VALUE),仍然会导致溢出问题。
        • 负数问题:如果 left + right 是负数,右移结果可能与预期不符。相加为负数的原因是:当两个正数相加的结果超过 int 的最大值时,最高位(符号位)会从 0 变为 1,导致结果被解释为负数。
    • 使用 >>>(无符号右移)

      >>>无符号右移操作符,它会忽略符号位,并在左侧补 0。

      int mid = (left + right) >>> 1;
      
      • 特点
        • 等价于除以 2(left + right) >>> 1 等价于将 left + right 视为无符号整数后除以 2。
        • 忽略符号位:无论 left + right 是正数还是负数,右移后结果都是正数。
        • 避免负数问题:即使 left + right 是负数,结果也会被正确处理。
      • 优点
        • 避免溢出问题>>> 可以正确处理 left + right 超过 int 最大值的情况。
          • 例如,left = 1_500_000_000right = 2_000_000_000left + right 会溢出为负数,但 (left + right) >>> 1 会得到正确的结果。
        • 性能优化:与 >> 类似,右移操作比除法更快。
    • 性能对比

      以下是几种计算方式的性能对比:

      计算方式性能是否可能溢出是否支持负数
      (left + right) / 2较慢
      left + (right - left) / 2较慢
      (left + right) >> 1较快
      (left + right) >>> 1较快

2 改动版

需求:在有序数组 A A A中查找值 t a r g e t target target,通过左闭右开区间优化边界处理。

  • 如果找到,则返回该值在数组中的索引。
  • 如果未找到,则返回 − 1 -1 1

2.1 算法描述

  1. 初始化:给定一个内含 n n n个元素的有序数组 A A A,满足 A 0 ≤ A 1 ≤ A 2 ≤ . . . ≤ A n − 1 A_{0} ≤ A_{1} ≤ A_{2} ≤ ... ≤ A_{n−1} A0A1A2...An1,一个待查值 t a r g e t target target
  2. 设置初始索引:设置 i = 0 i = 0 i=0 j = n j = n j=n左闭右开区间)。
  3. 终止条件:如果 i ≥ j i \geq j ij,结束查找,返回 − 1 −1 1
  4. 计算中间索引:设置 m = ⌊ i + j 2 ⌋ m = \lfloor \frac{i+j}{2} \rfloor m=2i+j
  5. 比较并调整索引
    • 如果 t a r g e t < A m target < A_{m} target<Am,设置 j = m j = m j=m(保持右开特性)。
    • 如果 A m < t a r g e t A_{m} < target Am<target,设置 i = m + 1 i = m + 1 i=m+1
    • 如果 A m = t a r g e t A_{m} = target Am=target,返回索引 m m m

2.2 算法流程图

target < A[m]
A[m] < target
A[m] = target
开始
设置 i=0, j=n
检查 i < j
返回 -1, 结束查找
计算 m = floor((i+j)/2)
比较 target 和 A[m]
设置 j = m
设置 i = m + 1
返回 m, 结束查找

2.3 算法实现

2.3.1 Java实现

/**
 * 使用二分查找算法查找目标值在数组中的索引
 * 如果目标值存在于数组中,则返回其索引;如果目标值不存在于数组中,则返回-1
 * 二分查找算法的前提是输入数组必须是有序的
 *
 * @param arr    一个有序的整数数组
 * @param target 目标值
 * @return 目标值在数组中的索引,如果目标值不存在于数组中,则返回-1
 */
public static int binarySearchAlternative(int[] arr, int target) {
    // TODO 1. 设置初始索引
    int left = 0; // 左边界
    int right = arr.length; // 右边界

    // TODO 2. 循环查找
    while (left < right) {
        // TODO 2.1. 计算中间索引
        int mid = (right + left) >>> 1;
        // TODO 2.2. 比较中间索引的值与目标值
        if (arr[mid] == target) {
            // TODO 2.3. 如果等于目标值,返回中间索引
            return mid;
        } else if (arr[mid] < target) {
            // TODO 2.4. 如果小于目标值,更新左边界
            left = mid + 1;
        } else {
            // TODO 2.5. 如果大于目标值,更新右边界
            right = mid;
        }
    }

    // TODO 3. 如果循环结束,没有找到目标值,返回-1
    return -1;
}

2.4 改进点分析

2.4.1 区间定义差异

特性基础版改进版
初始区间左闭右闭 [0, n-1]左闭右开 [0, n)
循环条件left <= rightleft < right
右边界更新方式right = mid - 1right = mid

2.4.2 核心改进原理

  1. 右开区间的优势
    • 更直观的索引计算:初始右边界直接取数组长度,无需-1调整
    • 减少边界条件判断:当right = mid时,天然保持右开特性
  2. 循环次数优化
    • 基础版终止条件为left > right,需要多一次无效循环
    • 改进版终止条件为left == right,精确控制循环次数

2.4.3 数学等价性证明

对于区间长度 L = j − i L = j - i L=ji

  • 基础版每次迭代减少 L / 2 L/2 L/2
  • 改进版每次迭代减少 ⌈ L / 2 ⌉ \lceil L/2 \rceil L/2

两种方式时间复杂度均为 O ( log ⁡ n ) O(\log n) O(logn),但改进版具有更好的空间局部性


3 平衡版

3.1 算法描述

  1. 初始化:给定一个内含 n n n 个元素的有序数组 A A A,满足 A 0 ≤ A 1 ≤ A 2 ≤ . . . ≤ A n − 1 A_0≤A_1≤A_2≤...≤A_{n−1} A0A1A2...An1,一个待查值 t a r g e t target target
  2. 设置初始索引:设置左边界 l e f t = 0 left=0 left=0,右边界$ right=n$(左闭右开区间)。
  3. 终止条件:如果 r i g h t − l e f t ≤ 1 right−left≤1 rightleft1,结束查找,返回 − 1 −1 1
  4. 计算中间索引:设置$ mid=\lfloor \frac{i+j}{2} \rfloor$。
  5. 比较并调整索引
    • 如果 t a r g e t < A m i d target<A_{mid} target<Amid,设置 r i g h t = m i d right=mid right=mid
    • 如果 A m i d < t a r g e t A_{mid}<target Amid<target,设置$ left=mid$。
    • 如果 A m i d = t a r g e t A_{mid}=target Amid=target,返回索引$ mid$。

3.2 算法流程图

target < A[mid]
A[mid] < target
A[mid] = target
开始
设置 left=0, right=n
检查 right - left > 1
返回 -1, 结束查找
计算 mid = (left + right) >>> 1
比较 target 和 A[mid]
设置 right = mid
设置 left = mid
返回 mid, 结束查找

3.3 算法实现

3.3.1 Java实现

/**
 * 使用平衡二分查找算法在已排序的数组中搜索指定目标值
 * 此方法通过不断缩小搜索范围的一半来高效查找目标值,适用于大规模数据集
 *
 * @param arr    已排序的整数数组,不包含重复元素
 * @param target 要搜索的目标值
 * @return 目标值在数组中的索引;如果目标值不在数组中,则返回-1
 */
public static int binarySearchBalanced(int[] arr, int target) {
    // 1. 设置初始索引
    int left = 0; // 左边界
    int right = arr.length; // 右边界

    // 2. 循环查找
    while (1 < right - left) {
        // 2.1. 计算中间索引
        int mid = (right + left) >>> 1;
        // 2.2. 比较中间索引的值与目标值
        if (arr[mid] < target) {
            // 2.4. 如果小于目标值,更新左边界
            left = mid;
        } else {
            // 2.5. 如果大于目标值,更新右边界
            right = mid;
        }
    }

    // 3. 最后检查左边界是否为目标值
    if (arr[left] == target) {
        // 3.1 如果等于目标值,返回左边界
        return left;
    } else {
        // 3.2 如果没有找到目标值,返回-1
        return -1;
    }
}

3.4 改进点分析

3.4.1 区间定义差异

特性基础版平衡版
初始区间左闭右闭$ [0, n-1]$左闭右开$ [0, n)$
循环条件left <= right1 < right - left
右边界更新方式right = mid - 1right = mid

3.4.2 核心改进原理

  1. 右开区间的优势
    • 更直观的索引计算:初始右边界直接取数组长度,无需 -1 调整。
    • 减少边界条件判断:当 right = mid 时,天然保持右开特性。
  2. 循环次数优化
    • 基础版终止条件为 left > right,需要多一次无效循环。
    • 平衡版终止条件为 1 < right - left,精确控制循环次数。

3.4.3 数学等价性证明

对于区间长度 L = j − i L=j−i L=ji

  • 基础版每次迭代减少 L / 2 L/2 L/2
  • 平衡版每次迭代减少 ⌈ L / 2 ⌉ ⌈L/2⌉ L/2

两种方式时间复杂度均为 O ( l o g n ) O(logn) O(logn),但平衡版具有更好的空间局部性。

3.4.4 性能分析

  • 时间复杂度 O ( l o g n ) O(logn) O(logn)。每次迭代将搜索范围缩小一半,因此查找效率非常高。
  • 空间复杂度 O ( 1 ) O(1) O(1)。算法只使用了常量级别的额外空间,适合大规模数据集。

4 对比总结

4.1 区间定义

版本初始区间循环条件右边界更新方式
基础版左闭右闭 [ 0 , n − 1 ] [0, n-1] [0,n1]left <= rightright = mid - 1
改进版左闭右开$ [0, n)$left < rightright = mid
平衡版左闭右开$ [0, n)$right - left > 1right = mid

4.2 中间索引计算

  • 基础版int mid = (left + right) >>> 1;
  • 改进版int mid = (left + right) >>> 1;
  • 平衡版int mid = (left + right) >>> 1;

4.3 终止条件

  • 基础版:当循环结束时,若未找到目标值,返回 − 1 -1 1
  • 改进版:当循环结束时,若未找到目标值,返回 − 1 -1 1
  • 平衡版:循环结束后,检查左边界是否为目标值。

4.4 改进点

  • 基础版:逻辑简单,易于理解。
  • 改进版
    • 使用左闭右开区间,右边界更新更直观。
    • 使用无符号右移运算符 >>> 计算中间值,避免整数溢出。
  • 平衡版
    • 通过左闭右开区间和特定的终止条件,进一步优化循环次数。
    • 最后检查左边界,减少不必要的比较。

4.5 性能分析

  • 时间复杂度
    • 所有版本均为$ O(log n)$,但在实际运行中,平衡版的循环次数更少,性能稍优。
  • 空间复杂度
    • 所有版本均为$ O(1)$,不占用额外空间。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2295372.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【如何掌握CSP-J 信奥赛中的深搜算法】

CSP-J 信奥赛中的深搜&#xff08;深度优先搜索&#xff09;算法是一个重要知识点&#xff0c;以下是一些学习深搜算法的建议&#xff1a; 理解基础概念 定义与原理&#xff1a;深度优先搜索是一种用于遍历或搜索图、树等数据结构的算法。它从起始节点开始&#xff0c;沿着一条…

Unity笔试常考

线程同步的几种方式 1.信号量pv操作 2.互斥加锁 3.条件变量 五层网络协议指的是哪五层 1.应用层 2.运输层 3.网络层 4.链路层 5.物理层 TCP和UDP区别 tcp 面向连接&#xff0c;保证发送顺序&#xff0c;速度慢&#xff0c;必须在线&#xff0c;三次握手&#xff0c;4次挥手…

Qt:Qt基础介绍

目录 Qt背景介绍 什么是Qt Qt的发展史 Qt支持的平台 Qt版本 Qt的优点 Qt的应用场景 Qt的成功案例 Qt的发展前景及就业分析 Qt背景介绍 什么是Qt Qt是⼀个跨平台的C图形用户界面应用程序框架。它为应用程序开发者提供了建立艺术级图形界面所需的所有功能。它是完全面向…

【deepSeek R1】Ollama 更改模型安装位置 以及应用安装位置

【deepSeek R1】Ollama 更改模型安装位置 以及应用安装位置 本地版部署deepSeek R1 可以参考文章 3分钟教你搭建属于自己的本地大模型 DeepSeek R1 Ollama 是一个开源工具&#xff0c;旨在帮助用户轻松在本地计算机上运行、部署和管理大型语言模型&#xff08;LLMs&#xff09;…

让office集成deepseek,支持office和WPS办公软件!(体验感受)

导读 AIGC:AIGC是一种新的人工智能技术&#xff0c;它的全称是Artificial Intelligence Generative Content&#xff0c;即人工智能生成内容。 它是一种基于机器学习和自然语言处理的技术&#xff0c;能够自动产生文本、图像、音频等多种类型的内容。这些内容可以是新闻文章、…

动态规划问题——青蛙跳台阶案例分析

问题描述&#xff1a; 一只青蛙要跳上n级台阶&#xff0c;它每次可以跳 1级或者2级。问&#xff1a;青蛙有多少种不同的跳法可以跳完这些台阶&#xff1f; 举个例子&#xff1a; 假设台阶数 n 3 &#xff0c;我们来看看青蛙有多少种跳法。 可能的跳法&#xff1a; 1. 跳1级…

MySQL 数据库编程-C++

目录 1 数据库基本知识 1.1 MYSQL常见命令 1.2 SQL注入 1.3 ORM框架 1 数据库基本知识 MySQL 为关系型数据库(Relational Database Management System), 这种所谓的"关系型"可以理解为"表格"的概念, 一个关系型数据库由一个或数个表格组成&#xff1a…

【大数据技术】搭建完全分布式高可用大数据集群(Flume)

搭建完全分布式高可用大数据集群(Flume) apache-flume-1.11.0-bin.tar.gz注:请在阅读本篇文章前,将以上资源下载下来。 写在前面 本文主要介绍搭建完全分布式高可用集群 Flume 的详细步骤。 注意: 统一约定将软件安装包存放于虚拟机的/software目录下,软件安装至/opt目…

kafka专栏解读

kafka专栏文章的编写将根据kafka架构进行编写&#xff0c;即先编辑kafka生产者相关的内容&#xff0c;再编写kafka服务端的内容&#xff08;这部分是核心&#xff0c;内容较多&#xff0c;包含kafka分区管理、日志存储、延时操作、控制器、可靠性等&#xff09;&#xff0c;最后…

深入探究 C++17 std::is_invocable

文章目录 一、引言二、std::is_invocable 概述代码示例输出结果 三、std::is_invocable 的工作原理简化实现示例 四、std::is_invocable 的相关变体1. std::is_invocable_r2. std::is_nothrow_invocable 和 std::is_nothrow_invocable_r 五、使用场景1. 模板元编程2. 泛型算法 …

OpenCV:图像修复

目录 简述 1. 原理说明 1.1 Navier-Stokes方法&#xff08;INPAINT_NS&#xff09; 1.2 快速行进方法&#xff08;INPAINT_TELEA&#xff09; 2. 实现步骤 2.1 输入图像和掩膜&#xff08;Mask&#xff09; 2.2 调用cv2.inpaint()函数 2.3 完整代码示例 2.4 运行结果 …

【项目日记(四)】thread cache 层

前言 前面我们对整个项目的框架进行了介绍&#xff0c;本期开始我们将进行第一层线程缓存层(thread cache)的详细介绍与实现。 目录 前言 一、thread cache 的整体设计 二、内存对齐规则和哈希映射关系 2.1 如何对齐&#xff1f; 2.2 这样设计对齐规则的好处&#xff1f…

人工智能图像分割之Mask2former源码解读

环境搭建: (1)首先本代码是下载的mmdetection-2022.9的,所以它的版本要配置好,本源码配置例如mmcv1.7,python3.7,pytorch1.13,cuda11.7。pytorch与python,cuda版本匹配可参考&#xff1a;https://www.jb51.net/python/3308342lx.htm。 (2)还有一个是先要安装一个vs2022版本或…

uniapp 编译生成鸿蒙正式app步骤

1&#xff0c;在最新版本DevEco-Studio工具新建一个空项目并生成p12和csr文件&#xff08;构建-生成私钥和证书请求文件&#xff09; 2&#xff0c;华为开发者平台 根据上面生成的csr文件新增cer和p7b文件&#xff0c;分发布和测试 3&#xff0c;在最新版本DevEco-Studio工具 文…

2024最新版Java面试题及答案,【来自于各大厂】

发现网上很多Java面试题都没有答案&#xff0c;所以花了很长时间搜集整理出来了这套Java面试题大全~ 篇幅限制就只能给大家展示小册部分内容了&#xff0c;需要完整版的及Java面试宝典小伙伴点赞转发&#xff0c;关注我后在【翻到最下方&#xff0c;文尾点击名片】即可免费获取…

Excel 融合 deepseek

效果展示 代码实现 Function QhBaiDuYunAIReq(question, _Optional Authorization "Bearer ", _Optional Qhurl "https://qianfan.baidubce.com/v2/chat/completions")Dim XMLHTTP As ObjectDim url As Stringurl Qhurl 这里替换为你实际的URLDim postD…

21.2.6 字体和边框

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 通过设置Rang.Font对象的几个成员就可以修改字体&#xff0c;设置Range.Borders就可以修改边框样式。 【例 21.6】【项目&#xff…

OpenFeign远程调用返回的是List<T>类型的数据

在使用 OpenFeign 进行远程调用时&#xff0c;如果接口返回的是 List 类型的数据&#xff0c;可以通过以下方式处理&#xff1a; 直接定义返回类型为List Feign 默认支持 JSON 序列化/反序列化&#xff0c;如果服务端返回的是 List的JSON格式数据&#xff0c;可以直接在 Feig…

三维模拟-机械臂自翻车

机械仿真 前言效果图后续 前言 最近在研究Unity机械仿真&#xff0c;用Unity实现其运动学仿真展示的功能&#xff0c;发现一个好用的插件“MGS-Machinery-master”&#xff0c;完美的解决了Unity关节定义缺少液压缸伸缩关节功能&#xff0c;内置了多个场景&#xff0c;讲真的&…

网络安全治理架构图 网络安全管理架构

网站安全攻防战 XSS攻击 防御手段&#xff1a; - 消毒。 因为恶意脚本中有一些特殊字符&#xff0c;可以通过转义的方式来进行防范 - HttpOnly 对cookie添加httpOnly属性则脚本不能修改cookie。就能防止恶意脚本篡改cookie 注入攻击 SQL注入攻击需要攻击者对数据库结构有所…