首页 > 编程语言 >C++ 中的 lowbit

C++ 中的 lowbit

时间:2024-07-11 13:08:30浏览次数:9  
标签:... 00 二进制 lowbit 补码 C++ 原码

lowbit 的定义

首先了解 lowbit 的定义

\(lowbit(n)\) ,为 \(n\) 的二进制原码中最低的一位 \(1\) 以及其后面的 \(0\) 所表示的数

举个简单的例子:

将 \(10\) 使用二进制表示为 \(1010\)

其中最低位的 \(1\) 为第2位(\(_{10}1_0\),从右往左数)

此时 \(lowbit(10)\) 使用二进制表示为 \(10\) ,即 \(2\) . (有关进制转换详见进制与进制转换


lowbit 的计算

如何计算 \(lowbit(n)\) 呢?

lowbit 的特殊情况

由于 lowbit 会将除最低位 \(1\) 以外所有的位 \(1\) 改为 \(0\) , lowbit 将只会对位 \(1\) 的位数高于1的二进制数产生影响,所以位 \(1\) 只有1位的二进制数和 \(0\) 处理后将得到原数据,即:

\[lowbit(2^n) = 2^n ~~~ ( n >= 0) \]

\[lowbit(0) = 0 \]

方法一:递归

先暂时假定 \(n\) 为正整数

将 \(n\) 转换为二进制,可得: \(00...00x...xxx\) ( \(x\) 为 \(0\) 或 \(1\))

此时 \(n · 2\) 转换为二进制可得: \(00...00x...xxx · 10 ~=~ 00...0xx...xx0\)

假设 \(n\) 转为二进制后,末尾有 \(m\) 个连续位 \(0\) (显然,此时 \(lowbit(n) ~ = ~ 2^m\) )

因此, \(n · 2\) 转为二进制后,末尾有 \(m + 1\) 个连续位 \(0\) (此时 \(lowbit(n · 2) ~ = ~ 2^{m + 1} ~ = ~ 2^m · 2\) )

于是我们得到了:

\[lowbit(n · 2) = lowbit(n) · 2 \]

此时 \(n · 2\) 表示为二进制是 \(00...0xx...xx0\) ,怎么样,有没有什么想法?

将 \(n · 2\) 加上 \(1\) ,得: \(00...0xx...xx0 + 1 ~ = ~ 00...0xx...xx1\)

显然:

\[lowbit(2n + 1) ~ = ~ 1 \]

观察 \(n\) 的二进制形式: \(00...00x...xxx\)

对比 \(-n\) 的二进制形式:\(10...00x...xxx\) (在原码中,我们一般使用第一位存储符号, \(0\) 为正, \(1\) 为负)

很明显, \(lowbit(n) ~ = ~ lowbit(-n)\)

无论 \(n\) 的符号为负还是为正,奇偶性都一致,因此,我们在上面推导出来的公式对负整数仍然成立

综上所述,任意奇数的 lowbit 值都为 \(1\) ,任意偶数的 lowbit 值都为其乘 \(0.5\) 得到的值的 lowbit 值乘 \(2\)

通过这种思路,我们可以编写一个递归函数计算 \(n\) 的 lowbit 值,遇到奇数直接返回 \(1\) ,遇到偶数辄除以 \(2\) 后继续计算

写成伪代码是这样的:

int lowbit(int n) {
    if (n % 2 == 1) return 1;  // Odd
    else return lowbit(n / 2) * 2;  // Even
}

方法二:公式

在方法一中,我们使用了深度优先搜索,时间复杂度可能有点高,我们当然可以使用记忆化数组降低复杂度,但,我们是否可以推导出一个公式直接计算,将复杂度降低为 \(O(1)\) 呢?

当然是可行的。还是观察 \(n\) 的二进制形式: \(00...00x...xxx\) (假定 \(n\) 为非负整数)

还是对比 \(-n\) 的二进制形式:\(10...00x...xxx\)

如果对 \(10...00x...xxx\) 每一位取反(符号位除外),我们就得到了 \(-n\) 的反码: \(11...11(-x)...(-x)(-x)(-x)\)

此时 \(-n\) 末尾的 \(0\) 全部变为 \(1\) ,而最低位的 \(1\) 也难逃变为 \(0\) 的命运

如果我们现在将其加上 \(1\) ,我们将得到 \(-n\) 的补码: \(11...11(-x)...(-x)(-x)(-x) + 1\) ,反码末尾的 \(1\) 将重新变为 \(0\) ,而最低位 \(0\) 将重新变为 \(1\) ,其他位不变,仍然是取反的状态,此时如果将 \(-n\) 的补码与 \(n\) 原码进行按位与的运算( \(n\) 与 \(-n\) 的原码只有符号位的不同),由于除符号位、最低位 \(1\) 及其后面的位 \(0\) ,其他位都进行了取反,这些位将被赋值为 0 & 11 & 0,即 \(0\) ,而符号位也会赋值为 0 & 1 ,只剩最低位 \(1\) 及其后面的位 \(0\) 分别被赋值为 1 & 10 & 0 ,即 \(1\) 和 \(0\) ,最后结果为 \(lowbit(n)\) (或者说 \(lowbit(-n)\))

那么 \(n\) 的反码、补码呢?上文所述的只是负数的反码与补码的计算方式,实际上,正数的原码、反码、补码都是一样的(对于原码、反码、补码,本博文已经进行了必要的解释,但如果你对其感兴趣,想知道其详细解释,可参考这篇博文:二进制|原码、反码、补码

众所周知, C++ 中,数字是使用补码表示的,因此,我们可以将 \(n\) 的补码视为 \(n\) 的原码,在 C++ 中进行运算。于是,我们得到了\(n\) 的原码和 \(-n\) 的补码

上文中,我们提到了将 \(n\) 的原码和 \(-n\) 的补码进行按位与运算可以得到 \(lowbit(n)\) 和 \(lowbit(-n)\) ,现在我们使用 \(n\) 的补码作为其原码(毕竟是一样的),可以得到:

\[lowbit(n) = -n & n \]

显然 \(n\) 是负数也不会造成影响

于是我们成功地得到了 lowbit 的计算公式,将算法的时间复杂度降低为 \(O(1)\) ,并简化了代码:

#define lowbit(n) (-n & n)

由于使用宏定义,一定要记得打括号,位运算的优先级是最低的


lowbit 的应用

lowbit 的应用也有很多,例如树状数组等,如果你对这方面感兴趣,不妨订阅一下我的博客,我以后会发布更多有趣且有用的算法知识

标签:...,00,二进制,lowbit,补码,C++,原码
From: https://www.cnblogs.com/bramble-marshall/p/18295941

相关文章

  • (免费领取源码)计算机毕业设计项目:宠物店管理系统 19849(开题答辩+程序定制+全套文案 )上
    目 录摘要1绪论1.1背景及意义1.2研究现状1.3springboot框架介绍2 宠物店管理系统系统分析2.1可行性分析2.2系统流程分析2.2.1数据流程3.3.2业务流程2.3系统功能分析2.3.1功能性分析2.3.2非功能性分析2.4系统用例分析2.5本章小结......
  • (免费领源码)Java/Mysql数据库+09536 SSM爱心捐赠物资维护系统,计算机毕业设计项目推荐上
    摘要随着信息技术的快速发展,计算机应用已经进入成千上万的家庭。随着物资数量的增加,物资库存管理也存在许多问题。物资数据的处理量正在迅速增加,原来的手工管理模式不适合这种形式。使用计算机可以完成数据收集、处理和分析,减少人力和物力的浪费。需要建立爱心捐赠物资维护系......
  • C++内存管理
    1内存概述1.1分配方式在C++中,内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。栈,在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量......
  • C++ 避免内存泄露的手段和措施
    在C++中,内存泄露是一个常见问题,指的是已分配的内存由于某种原因未被释放,导致程序无法再次使用这部分内存。为了避免内存泄露,C++提供了多种手段和措施,主要包括以下几种:智能指针(SmartPointers):智能指针是C++标准库中的一部分,用于自动管理内存,确保在适当的时候释放内存。......
  • Qt入门(C++)
    创建项目基类的选择对于基类的选择有三个选项,分别是QMainWindow、QWidget、QDialog基类说明QMainWindow主窗⼝类,⼀般⽤于较为复杂的应⽤程序,除了中央客⼾区界⾯,还包括菜单栏、⼯具栏、状态栏以及多个可停靠的⼯具对话框等QWidget最简单、最基本的窗体程序,⾥⾯可以放置多......
  • C++函数模板学习
    函数模板是C++中的一个强大特性,允许编写通用函数来处理不同的数据类型。学习函数模板有助于理解泛型编程的概念,提高代码的可重用性和可维护性。以下是一些学习函数模板时可以关注的方面:1.模板的基本概念模板定义:了解如何定义模板函数和模板类。模板参数:掌握模板参数的使......
  • [C++] C++20约束表达式和requires子句
    约束约束是逻辑操作和操作数的序列,它指定了对模板实参的要求。合取两个约束的合取是用&&运算符。template<typenameT>conceptluser=std::integral<T>&&std::signed_integral<T>;需要约束同时满足两个要求。合取判断的时候,使用短路检测,即对std::integra......
  • 「字符串」Manacher算法(马拉车)/ LeetCode 05(C++)
    给你一个字符串 s,找到 s 中最长的回文子串。示例1:输入:s="babad"输出:"bab"解释:"aba"同样是符合题意的答案。示例2:输入:s="cbbd"输出:"bb"思路我们回想中心扩散法:某字符处的中心扩散完毕后,其实已经将它身前身后的字符段落都搜索过了,那么如果我们搜索其后的字......
  • c++ protobuf安装记录
    googleprotobuf是一个灵活的、高效的用于序列化数据的协议。相比较XML和JSON格式,protobuf更小、更快、更便捷。googleprotobuf是跨语言的,并且自带了一个编译器(protoc),只需要用它进行编译,可以编译成Java、python、C++、C#、Go等代码,然后就可以直接使用,不需要再写其他代码,自带有......
  • 【C++修行之道】string类练习题
    目录387.字符串中的第一个唯一字符125.验证回文串 917.仅仅反转字母415.字符串相加(重点)541.反转字符串II387.字符串中的第一个唯一字符字符串中的第一个唯一字符-力扣(LeetCode)给定一个字符串 s ,找到它的第一个不重复的字符,并返回它的索引 。如果不存......