《算法零基础100讲》(第49讲) 位运算 (右移)
Posted 英雄哪里出来
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《算法零基础100讲》(第49讲) 位运算 (右移)相关的知识,希望对你有一定的参考价值。
文章目录
零、写在前面
这是《算法零基础100讲》 专栏打卡学习的第四十九天了。
每天打卡的题,做不出来没关系,因为困难的题涉及知识点较多,后面还是会开放出来的,就像昨天的 最大公约数 那道题今天还是会有,所以不要着急,内容能看懂,能自己分析,能做出简单题,就可以打卡。
在刷题的过程中,总结自己遇到的坑点,写出 「 解题报告 」 供他人学习,也是一种自我学习的方式。这就是经典的帮助他人的同时,成就自己。目前, 「 万人千题 」 社区 每天都会有五六篇高质量的 「 解题报告 」 被我 「 加精 」。如果觉得自己有能力的,也可以来发布你的 「 解题报告 」。千万级流量,你我共同拥有。
一、概念定义
1、右移运算符的定义
1)右移的二进制形态
右移运算符是一个二元的位运算符,也就是有两个操作数,表示为x >> y
。其中x
和y
均为整数。
x >> y
念作:“将
x
x
x 右移
y
y
y 位”,这里的位当然就是二进制位了,那么它表示的意思也就是:先将
x
x
x 用二进制表示,对于正数,右移
y
y
y 位;对于负数,右移
y
y
y 位后高位都补上 1。
举个例子:对于二进制数
8
7
10
=
(
1010111
)
2
87_10 = (1010111)_2
8710=(1010111)2 左移
y
y
y 位的结果就是:
(
1010
)
2
(1010)_2
(1010)2
2)右移的执行结果
x >> y
的执行结果等价于:
⌊
x
2
y
⌋
\\lfloor \\frac x 2^y \\rfloor
⌊2yx⌋其中
⌊
a
⌋
\\lfloor a\\rfloor
⌊a⌋ 代表对
a
a
a 取下整。如下代码:
#include <stdio.h>
int main()
int x = 0b1010111;
int y = 3;
printf("%d\\n", x >> y);
return 0;
输出结果为:10
正好符合这个右移运算符的实际含义:
10
=
⌊
87
2
3
⌋
10 = \\lfloor \\frac 87 2^3 \\rfloor
10=⌊2387⌋
由于除法可能造成不能整除,所以才会有 取下整 这一步运算。
3)负数右移的执行结果
所谓负数右移,就是x >> y
中,当x
为负数的情况,代码如下:
#include <stdio.h>
int main()
printf("%d\\n", -1 >> 1);
return 0;
它的输出为:-1
。
我们发现同样是满足
⌊
x
2
y
⌋
\\lfloor \\frac x 2^y \\rfloor
⌊2yx⌋ 的(注意,负数的 取下整 和 正数 是正好相反的),这个可以用补码来解释,-1
的补码为:
11111111
11111111
11111111
11111111
11111111 \\ 11111111 \\ 11111111 \\ 11111111
11111111 11111111 11111111 11111111 右移一位后,由于是负数,高位补上 1,得到:
11111111
11111111
11111111
11111111
11111111 \\ 11111111 \\ 11111111 \\ 11111111
11111111 11111111 11111111 11111111 而这,正好是 -1
的补码,同样,继续右移 1 位,得到。可以理解成 - (x >> y)
和(-x) >> y
是等价的。
【例题1】要求不运行代码,肉眼看出这段代码输出多少。
#include <stdio.h>
int main()
int x = (1 << 31) | (1 << 30) | 1;
int y = (1 << 31) | (1 << 30) | (1 << 29);
printf("%d\\n", (x >> 1) / y);
return 0;
4)右移负数位是什么情况
刚才我们讨论了
x
<
0
x < 0
x<0 的情况,那么接下来,我们试下
y
<
0
y < 0
y<0 的情况会是如何?
是否同样满足:
⌊
x
2
y
⌋
\\lfloor \\frac x 2^y \\rfloor
⌊2yx⌋ 呢?
如果还是满足,那么两个整数的左移就有可能产生小数了。
看个例子:
#include <stdio.h>
int main()
printf("%d\\n", 1 >> -1); // 2
printf("%d\\n", 1 >> -2); // 4
printf("%d\\n", 1 >> -3); // 8
printf("%d\\n", 1 >> -4); // 16
printf("%d\\n", 1 >> -5); // 32
printf("%d\\n", 1 >> -6); // 64
printf("%d\\n", 1 >> -7); // 128
return 0;
虽然能够正常运行,但是结果好像不是我们期望的,而且会报警告如下:
[Warning] right shift count is negative [-Wshift-count-negative]
实际上,编辑器告诉我们尽量不用右移的时候用负数,但是它的执行结果不能算错误,起码例子里面对了。右移负数位其实效果和左移对应正数数值位一致。
左移相关的内容,可以参考:《算法零基础100讲》(第48讲) 位运算 (左移)。
2、右移运算符的应用
1)去掉低 k 位
【例题2】给定一个数 x x x,去掉它的低 k k k 位以后进行输出。
这个问题,可以直接通过右移来完成,如下:x >> k
。
2)取低位连续 1
【例题3】获取一个数 x x x 低位连续的 1 并且输出。
对于一个数
x
x
x,假设低位有连续
k
k
k 个 1。如下:
(
.
.
.
0
1...1
⏟
k
)
2
(...0\\underbrace1...1_\\rm k)_2
(...0k
1...1)2 然后我们将它加上 1 以后,得到的就是:
(
.
.
.
1
0...0
⏟
k
)
2
(...1\\underbrace0...0_\\rm k)_2
(...1k
0...0)2 这时候将这两个数异或结果为:
(
1...1
⏟
k
+
1
)
2
(\\underbrace1...1_\\rm k+1)_2
(k+1
1...1)2
这时候,再进行右移一位,就得到了 连续
k
k
k 个 1 的值,也正是我们所求。所以可以用以下语句来求:(x ^ (x + 1)) >> 1
。
3)取第k位的值
【例题4】获取一个数 x x x 的第 k ( 0 ≤ k ≤ 30 ) k(0 \\le k \\le 30) k(0≤k≤30) 位的值并且输出。
对于二进制数来说,第
k
k
k 位的值一定是 0 或者 1。
而 对于 1 到
k
−
1
k-1
k−1 位的数字,对于我们来说是没有意义的,我们可以用右移来去掉,再用位与运算符来获取二进制的最后一位是 0 还是 1,如下:(x >> k) & 1
。
二、题目描述
给你一个整数 n n n,请你判断该整数是否是 2 2 2 的幂次方。如果是,返回
true
;否则,返回false
。如果存在一个整数 x x x 使得 n = = 2 x n == 2^x n==2x,则认为 n n n 是 2 2 2 的幂次方。
三、算法详解
这道题在这个专栏里已经出现了无数次了,因为它确实可以 “一题多做”。这次的用法,就是利用迭代的方式,不断将数右移一位,如果得到的结果为 1,就是 2 的幂;如果不为一,但是是奇数,则表示一定不是 2 的幂。
四、源码剖析
bool isPowerOfTwo(int n)
int i;
if(n <= 0)
return false;
while(1)
if(n == 1)
return true; // (1)
if(n & 1)
return false; // (2)
n >>= 1; // (3)
return false;
- ( 1 ) (1) (1) 代表最高位为 1,其它低位都是 0;
- ( 2 ) (2) (2) 代表 1 出现在非最高位的位置上;
- ( 3 ) (3) (3) 执行一次右移;
五、推荐专栏
六、习题练习
序号 | 题目链接 | 难度 |
---|---|---|
1 | 位1的个数 | ★☆☆☆☆ |
2 | 2的幂 | ★☆☆☆☆ |
3 | 4的幂 | ★☆☆☆☆ |
4 | 数字转换为十六进制数 | ★★☆☆☆ |
以上是关于《算法零基础100讲》(第49讲) 位运算 (右移)的主要内容,如果未能解决你的问题,请参考以下文章