【算法】二分查找与二分答案

二分查找是一种高效的搜索算法,适用于有序数组。它通过不断将搜索区间减半来定位目标值。文章介绍了二分查找的基本概念,包括其工作原理、核心代码示例以及在解题中的应用,如二分答案。同时,讨论了在不同情况下如何调整搜索区间的边界,以避免死循环。此外,还提到了二分查找在最大值最小化/最小值最大化问题中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

二分法是一种查找效率较高的方法,在编程中十分常见。话不多说,现在,我们来学习一下二分吧 •ࡇ•


1什么是二分

1.1二分查找(Binary Search)

1.1.1基础认知

  • 顾名思义,“二分”就是将数组劈成两半,每一次总是查找中间的数。也叫折半搜索。数字炸弹的游戏大家应该都玩过,如果按顺序猜数,效率肯定没有从中间猜那么高。

  • 下面是二分查找的核心步骤。

①初始状态数组的首尾各有一个标记,分别记为leftright

(根据不同习惯也可使用low和high)。

②循环条件while(left<right)。因为leftright在二分未找到key的过程中总是相互靠近,直至指向同一个位置。所以left==right时循环才停止。

③判断条件升序数组的二分查找为例,将a[m]与要查找的数key作比较。

  • 如果a[m]小于key,说明key在a[m]右侧,则将数组从左向右压缩一半,把left用力向右推,推到m的右边,也就是第6位,相当于把左边的数据都屏蔽了,因为key不可能在左半边。

  • 同理,如果a[m]大于key,说明key在a[m]左侧,则将数组从右向左压缩一半,把right用力向左推,推到m的左边,也就是第4位,key不可能在右半边。

  • 本例中,假设key=36

  • 二分查找有一个大前提数组必须有序

很显然,如果数组乱序,那么比较后标记leftright的移动就是混乱的。

比如在下图这个乱序的数组中查找位于第9位的"4",那么第一次查找后right就会移动到m前面第4位的位置上,下一次就只能在下标1~4的序列中查找,就不可能查找到第9位的正确位置。这样的话,二分查找就没有意义。

1.1.2核心代码

下面是用C++语言描述的最简单的二分查找代码:


int binary_search(int start, int end, int key) {
  int ret = -1;  // 未搜索到数据返回-1下标
  int mid;
  while (start <= end) {
  //mid = (end - start)/2; 直接平均的一般写法
    mid = start + ((end - start) >> 1);  // 直接平均可能会溢出,所以用这个算法
    if (arr[mid] < key)
      start = mid + 1;
    else if (arr[mid] > key)
      end = mid - 1;
    else {  // 最后检测相等是因为多数搜索情况不是大于就是小于
      ret = mid;
      break;
    }
  }
  return ret;  // 单一出口
}
第5,6行注释:对于n是有符号数的情况,当你可以保证 n>=0时,n >> 1 比 n / 2 指令数更少。

以上就是二分查找的基础内容。


1.2二分答案

解题的时候往往会考虑枚举答案然后检验枚举的值是否正确。若满足单调性,则满足使用二分法的条件。把这里的枚举换成二分,就变成了 「二分答案」

这里以一个经典的二分答案题目为例(来自洛谷-二分题单P1873 [COCI 2011/2012 #5] EKO / 砍树

题目理解上并不困难,就是在可能解的范围[0, max]内进行二分,每次假定H = (l + r + 1) / 2为答案,然后根据这个假设的答案H,结合贪心法来验证能否得到相应多的木材。

  • Q:那么,为什么是H = (l + r + 1) / 2而不是H = (l + r ) / 2呢?

这里其实并不能单独把这个语句拎出来考虑,而要结合下面l和r的移动方式(要不要+1/-1)。这就是二分的偏向区间移动有机结合的灵活性所在,也是题目能否正确运行出结果的关键。

1. 如果上面H =(l + r) / 2, H偏向l,则l不能等于H,必须为l=H+1,因为可能会出现 l一直小于r的情况,导致死循环。
2. 但改成H = (l + r + 1) / 2, 使H偏向r,则可以使l=H,因为当处于l=r-1的位置时,H偏向r,则l能够跟随H偏向右移动,可以走到最后l=r使循环结束。

以下是AC代码,可以手动模拟一下不同写法下,走到l和r相邻时的情况,能更直观地感受和理解上述的灵活性。(动手动脑噢!ᕙ(`▿´)ᕗ)


using namespace std;
#include <iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
long long n, m;//树木数量n,需要木材m
long long a[1000005];

int main()
{
    int i;
    long long max = 0;  //注意数据范围,int是不够的,会溢出
    long long l, r;
    long long sum, top, H;
    scanf("%lld%lld", &n, &m);
    for (i = 1; i <= n; i++) {
        scanf("%lld",&a[i]);
        if (a[i] > max)max = a[i]; //记录最高的树高为max
    }
    l = 0; r = max;
    while (l < r) {
        H = (l + r + 1) / 2;
        sum = 0;
        for (i = 1; i <= n; i++) {
            //每棵树被砍掉的部分长度相加
            top = ((a[i] - H) >= 0) ? (a[i] - H) : 0;
            sum += top;
        }
        if (sum < m)r = H - 1;
        else if (sum == m) {
            printf("%lld", H);
            break;
        }
        else l = H; //如果上面H =(l + r) / 2,则l不能等于H,必须l=H+1,因为可能会出现l一直小于r
                  //但改成H = (l + r + 1) / 2,使H偏向r,则可以使l=H,因为当处于l=r-1的位置时,H偏向r,则l向右移动可以有l=r使循环结束。
    }
    if (l == r) printf("%lld", l);
    return 0;
}

(以下两问答摘录自OI Wiki)

  1. 为何搜索区间是左闭右开的?

因为搜到最后,会这样(以合法的最大值为例):

然后会

合法的最小值恰恰相反。

  1. 为何返回左边值?

同上。L总是最优解


1.3最大值最小化/最小值最大化

这一类题目中我们要求一个二分区间序列,左侧或右侧中的一侧满足某种条件,但另一侧都不满足某种条件。

注意,这里的有序是广义的有序,如果一个数组中的左侧或者右侧都满足某一种条件,而另一侧都不满足这种条件,也可以看作是一种有序(如果把满足条件看做0,不满足看做1,至少对于这个条件的这一维度是有序的)。换言之,二分搜索法可以用来查找满足某种条件的最大(最小)的值。

要求满足某种条件的最大值的最小可能情况(最大值最小化),首先的想法是从小到大枚举这个作为答案的「最大值」,然后去判断是否合法。若答案单调,就可以使用二分搜索法来更快地找到答案。因此,要想使用二分搜索法来解这种「最大值最小化」的题目,需要满足以下三个条件:

  1. 答案在一个固定区间内;

  1. 可能查找一个符合条件的值不是很容易,但是要求能比较容易地判断某个值是否是符合条件的;

  1. 可行解对于区间满足一定的单调性。换言之,如果x是符合条件的,那么有x-1或者x+1也符合条件。(这样下来就满足了上面提到的单调性)

例题

这里有一道题要求我们查找满足条件的最小值的最大值,来自洛谷二分题单的P2678

一开始写的时候,由于是第一次接触这类最小值最大化的题目,所以误入了歧途,被移动的最大石头数量限制住了思维,试图从提前计算出间隔大小来入手,并且将移动石头的个数严格限制在了M以内,所以思路就变得很乱,很难下手,效率也不高。

后来参考了这一篇题解:题解 P2678 【跳石头】 - ShawnZhou 的博客 - 洛谷博客 (luogu.com.cn)

(写得很好,通俗易懂,很详细)

总结出这一类题的核心

  1. 可行解的范围[l, r]内进行二分,二分的判断依靠judge函数

  1. 保留满足条件的可行解暂时存储为答案ans,然后接着向更可能有可行最优解的方向(条件更苛刻)继续二分。

  1. 如果二分到的答案是非法解,就往更可能有可行解的方向(条件更宽松)继续二分。

  1. judge函数的设计根据题意而异,但大体上的思想都是——想办法检测这个解是不是合法

本题中,我们去判断如果以这个距离为最短跳跃距离需要移走多少块石头,先不必考虑限制移走多少块,等全部拿完再把拿走的数量和限制进行比对,如果超出限制,那么这就是一个非法解,反之就是一个合法解。

main函数的二分部分中,要注意 二分的结束条件(l<=r)l,r的移动(l=m+1和r=m-1),这两部分的设计需要考虑极端情况下的处理,例如l和r相邻时或者l==r时,m的左右偏向会不会导致死循环等。最简单的处理办法就是模拟这样的情况,仔细思考哪种写法能够满足题目要求且不会导致死循环。需要自己去思考。

AC代码如下,建议读完题解后配合注释食用。同样请读者们思考偏向和区间移动的关系。◕‿◕


using namespace std;
#include <iostream>
int L, N, M;  //L总距离,N岩石数,M至多移走的岩石数
int d[50005]; //每块岩石与起点的距离,升序

/*judge函数,用于判断解是否合法*/
bool judge(int m) { //m:二分出后假定的答案
    int move = 0;  //move:计数器,记录移走的岩石数
    int now = 0;   //**now:表示模拟跳石头的人当前所在位置**
    for (int i = 1; i <= N + 1; i++) { //i表示下一块石头的编号
        //**N不是终点,N+1才是!**
        //遍历,模拟跳一趟
        if (d[i] - d[now] < m) move++;//如果本次跳跃的间隔小于假设的的结果m
                                      //则移走,不用跳,然后考虑下一块石头。
        else now = i;                //如果这块石头不用拿走,则跳到下一块石头。
    }
    if (move > M)return false; //移走的石头个数大于规定值时返回false
    else return true;          //反之则m是可行解,返回true
}
/*main函数,主要是二分的过程*/
int main()
{
    int l, r, m;
    int ans = 0; //ans最终输出的结果
    cin >> L >> N >> M;
    for (int i = 1; i <= N; i++) {
        cin >> d[i];
    }
    d[N + 1] = L;
    l = 1;
    r = L;
    while (l <= r) {
        m = (r + l) / 2;
        if (judge(m)) {
            //如果移动的石头小于等于M,则此解可行。
            ans = m;
            l = m + 1;//**为什么不是l = m?** 这里又涉及偏向的问题。
        }
        else {
            //如果以m为答案移动的石头数大于限定值M,则此解m太大,非法,向左找
            r = m - 1;
        }
    }
    cout << ans;
    return 0;
}

二分的有关知识和思考就写到这里啦!大家多写题,多思考,多进步!❛‿˂̵✧

欢迎指出错误和友好讨论~

<think>好的,用户想了解RCAB模块在图像超分辨率任务中的作用和原理。首先,我需要回忆一下RCAB的相关知识。RCAB全称是Residual Channel Attention Block,应该是在残差块的基础上加入了通道注意力机制。这通常用于提升超分辨率模型的性能。 记得残差块(Residual Block)在ResNet中被广泛使用,通过跳跃连接缓解梯度消失的问题,帮助训练更深的网络。而通道注意力机制,比如SENet中的Squeeze-and-Excitation模块,能够自动学习各个通道的重要性,增强有用的特征,抑制不重要的。RCAB可能结合了这两者。 接下来,需要确认RCAB的具体结构。根据常见的设计,RCAB可能包含多个卷积层,然后是通道注意力模块,最后通过残差连接相加。这样可以在提取特征的同时,动态调整各通道的权重,让网络更关注重要的信息。 在超分辨率任务中,RCAB的作用应该是增强高频细节的恢复,比如边缘和纹理,这对于提升图像清晰度至关重要。同时,通道注意力机制能有效利用通道间的依赖关系,优化特征表达,从而提升重建质量。 应用场景方面,RCAB可能被用于如EDSR、RCAN等超分辨率模型中。这些模型通常堆叠多个RCAB模块,通过深层的网络结构逐步恢复高分辨率图像。特别是在处理大倍数超分辨率(如4x、8x)时,RCAB能帮助保持细节,减少模糊。 还需要考虑RCAB的优势。比如,通过残差学习简化了优化过程,而通道注意力则增强了特征的区分能力。这可能使得模型在计算资源有限的情况下仍能保持高效,同时提升性能。 需要验证这些信息是否正确。参考用户提供的资料,引用[1]提到多尺度卷积和特征融合,引用[2]提到信息交互和注意力机制。虽然没有直接提到RCAB,但通道注意力属于注意力机制的一种,符合引用中的信息。因此可以推断RCAB的应用场景这些模块类似,用于特征提取和优化。 总结来说,RCAB的作用是通过残差结构和通道注意力机制,有效提取并优化特征,提升超分辨率重建的质量。应用场景包括各种基于深度学习的超分辨率模型,特别是在需要恢复细节和高效利用特征时。</think>RCAB(Residual Channel Attention Block)模块在图像超分辨率任务中主要用于**增强特征表达能力并优化高频细节恢复**,其核心原理是通过残差学习通道注意力机制的结合,动态调整特征通道的重要性权重。 ### 作用原理分步说明: 1. **残差学习框架** RCAB基于残差块(Residual Block)设计,包含两个卷积层和激活函数,通过跳跃连接(Skip Connection)将输入输出相加。这种方式缓解了梯度消失问题,支持更深的网络训练,公式为: $$F(x) = H(x) + x$$ 其中$H(x)$为残差函数,$x$为输入特征。 2. **通道注意力机制** 在残差块后引入通道注意力模块(Channel Attention Module),通过全局平均池化(Global Average Pooling)统计通道特征,并利用全连接层生成通道权重: $$w_c = \sigma(W_2 \cdot \delta(W_1 \cdot \text{GAP}(x)))$$ 其中$\sigma$为Sigmoid函数,$\delta$为ReLU,$W_1$和$W_2$为全连接层参数。最终输出特征为各通道加权后的结果: $$x' = w_c \cdot x$$ 该机制使网络自动关注对重建更重要的特征通道[^1]。 3. **多尺度特征融合** RCAB常多尺度特征提取模块结合(如引用[1]中提到的多尺度卷积层),通过不同感受野的卷积核捕获低频和高频信息,再利用通道注意力筛选关键特征,减少冗余计算,提升重建效率。 ### 应用场景 - **单图像超分辨率(SISR)**:如RCAN(Residual Channel Attention Networks)模型,通过堆叠多个RCAB模块恢复高分辨率图像的细节[^2]。 - **多模态超分辨率融合**:在红外可见光图像融合任务中,RCAB用于增强跨模态特征的表征能力,保留边缘和纹理信息[^1]。 - **轻量超分辨率网络**:由于通道注意力可抑制无效特征,RCAB在减少计算量的同时保持性能,适用于移动端部署。 ### 示例模型结构 ```python class RCAB(nn.Module): def __init__(self, channels): super().__init__() self.conv1 = nn.Conv2d(channels, channels, 3, padding=1) self.conv2 = nn.Conv2d(channels, channels, 3, padding=1) self.ca = ChannelAttention(channels) # 通道注意力模块 def forward(self, x): res = self.conv1(x) res = F.relu(res) res = self.conv2(res) res = self.ca(res) # 通道加权 return x + res # 残差连接 ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值