Task01 关于运算符优先级和位运算的学习研究 复习笔记 ——阿里云天池

学习目标:

复习Task01内容,并对关键内容进行总结


学习内容:

主要复习了运算符优先级、位运算两块内容,对运算符的优先级进行了背诵和记忆,对位运算相关知识进行了总结

首先说明的是,本文所有内容是本人对python客观性能的主观分析,只对python语言的宏观表现加以理解,目的在于更好更方便的推理出相关语句的行为结果,可能与实际的python解释器的底层行为大有不同。


学习时间:

2020/12/2 21:00–23:00
2020/12/3 12:00-15:00


学习产出:

运算符优先级研究

优先级

在这里插入图片描述
我根据自己的记忆习惯,把这些运算符分成了三个优先级梯队:
1.最优先梯队:()、x[i]、x.attr
小括号自然不必多说,在不清楚优先级顺序的情况下可以用来保证正确的运算顺序
其次是索引运算符和属性运算符,这两个运算符都是放置于操作变量的右边的,对于一个操作数来说同一方向的运算符,哪个距离它近,先执行哪个,所以这两个运算符的优先级比较没有太大意义
2.第二梯队:**、~、±(正负号)
除了乘方运算符,取反和正负号都是一元运算符,独特的乘方运算符恰好是第二梯队的最优先运算符
而由于第三梯队的运算符都是二元运算符,所以“一元运算符通常优先于二元运算符”
由于取反和正负号都在操作数左边,所以他们的优先级比较也没有太大意义,虽然规定~优先于正负号,但是还是哪个运算符距离操作数近,先执行哪一个,示例代码如下
在这里插入图片描述
3.第三梯队:大多数二元运算符
从高到低:算、位、比、逻、逗
算术运算符、位运算符、比较运算符、逻辑运算符、逗号运算符

通过将多种运算符分成梯队记忆,可以快速掌握知识点
相信有很多C++、java基础的同学可能在想,自增运算符(++)和自减运算符(–)呢?
值得庆幸的是,python舍弃了++和–,所以我们不需要研究这两个运算符的复杂运算顺序了

结合性

知晓这些运算符的优先级,我们便可以理解的绝大多数表达式的运算顺序,但并不是全部
还需要补充一点的知识是运算符的结合性

什么是运算符的结合性呢

所谓结合性,就是当一个表达式中出现多个优先级相同的运算符时,先执行哪个运算符:先执行左边的叫左结合性,先执行右边的叫右结合性

例如对于表达式对于100 / 25 * 16,/和*的优先级相同,应该先执行哪一个呢?这个时候就不能只依赖运算符优先级决定了,还要参考运算符的结合性。/和*都具有左结合性,因此先执行左边的除法,再执行右边的乘法,最终结果是 64。

Python 中大部分运算符都具有左结合性,也就是从左到右执行;只有 ** 乘方运算符、单目运算符(例如 not 逻辑非运算符)、赋值运算符和三目运算符例外,它们具有右结合性,也就是从右向左执行

上述情况也许并不能很有效地帮助我们理解运算符结合的必要性,再举一个例子:2**1**2
结果是多少呢?根据结合性分析,**具有右结合性,所以先运算1**2=1,再运算2**1=2,所以结果应为2
验证代码如下:
在这里插入图片描述

位运算研究

众所周知,数据在计算机内部以二进制形式存储,每一个位(bit)均有两种状态(0和 1),而位运算就是以位为单位进行的操作,处理的数据只有0和1.

1.按位与&

为了方便理解,并且与义务教育的数学知识相结合,我们可以假设0为假,1为真。按位与可以理解为“且”。对于两个命题A和B,A且B为真等价于什么?(高中数学)很显然是A为真并且B为真,同样的对于按位与运算,A&B等于1(为真)等价于A是1且B是1;换言之只要两个操作数有一个为0,那么结果为0.
代码验证如下:
在这里插入图片描述

2.按位或|

类似于按位与的分析理解方式,两个命题A和B,他们的组合命题A或B为真的等价于什么?显然是A为真或者B为真,对于按位或运算,只要两个操作数有一个为1,那么结果为1;换言之,两个操作数都为0时,结果才为0.
代码验证如下:
在这里插入图片描述

3.按位取反~

取反这个概念顾名思义,就是把状态变成反状态,一个位只有0和1两种状态,所以对0取反得1,对1取反得0.
在python中,二进制数据以补码形式存储,最高位代表符号位,0代表正,1代表负。
对任意一个数的位运算(包括与或非等等),都是对这个数的补码的所有位进行运算。上面两个运算符操作中,0和1两个数除最后一位之外,其他的位包括符号位均在运算中不会改变,因此可以演示对一位进行位运算的结果。而取反必然将使得符号位发生改变,所以很难演示只对一个位取反的结果
因此我们在上述理论基础下,直接研究对一个多位二进制数的取反
设a=0b110(原码,python是根据0b前面的正负号来设定符号位的,所以符号位是0而不是1,原码写全可以看成0110),a在十进制的值是6,由于位运算是对补码的运算,所以我们要研究a的补码是多少。对于正数,补码、反码、原码相同,所以对0110取反,得到1001。值得注意的是,前面提到位运算是对补码进行运算,还有一点需要加以说明,位运算的结果仍然是补码,也就是说1001是运算结果的补码。符号位为1说明这是一个负数的补码,对于负数来讲,反码是原码除符号位之外的位取反,补码是反码加一。这里我们由补码推原码,自然要先减去1,得1000,再对符号位之外的位取反,得1111(原码),即-7.
代码验证:
在这里插入图片描述
bin()函数是返回的带正负号的原码,所以bin(~a)应该为-0b111,代码验证:
在这里插入图片描述
下面我们再推理一个负数取反的例子,设a=-0b110(原码1110),反码为1001,补码为1010,对补码进行按位取反操作,得结果得补码为0101(正数),推得结果得原码为0101,即5,代码验证如下:
在这里插入图片描述
至此,我们对6对反得到-7,对-6取反得到5。
每一次推导取反运算都太过于繁琐,为了能快速得到取反得结果值,下面我们对取反前后的十进制数值关系做研究。

为了不失一般性,我们设参与取反运算的二进制数a包括符号位一共是n位,a的原码为a(原),a的反码为a(反),a的补码为a(补),a的十进制数值为a(10)。
取反之后结果的补码为b(补),反码为b(反),原码为b(原),十进制数值为b(10)
下面运算,我们将把第n位的符号位看作数值为参与运算,这样子可以把运算统一为二进制数的加法运算,但是最后要对结果做一个从无符号到有符号的转换
由条件定义得b(10)=~a(10)
由取反位运算是对补码操作得a(补)+b(补)=111…111(n位) (取反操作是0边1,1边0,包括符号位)
即a(补)+b(补)=2^n-1
由于取反会使符号位发生改变,所以我们不妨设a的符号位为0。
所以,a(原)=a(反)=a(补)
取反之后,b的符号位是1
所以对于b来说,b(补)=b(反)+1 b(反)+b(原)=(2^n)-1+2^(n-1)
所以,a(补)+b(补)=a(原)+b(反)+1=a(原)+(2^n)-1+2^(n-1)-b(原)+1=a(原)-b(原)+2^n+2^(n-1)=2^n-1
即b(原)=a(原)+2^(n-1)+1
值得注意的是,我们假设的a第n位的符号位是0,而2^(n-1)(无符号二进制)位1000…000(第n位是1),最后我们要做一个从无符号推理到有符号的转换,而a(原)+1000…0000,即把a(原)的符号位从0变成1,从正到负。最后还有个加一操作,这里的加一是无符号运算,比如1010+1等于1011,所以对于一个负数+00001,等价于在考虑符号情况下的数值上-1。
所以最后推理的结果应该为
b(10)=-a(10)-1

这里推理结果,符合之前两个例子,我们不妨再举一例验证:
在这里插入图片描述

位运算使用场景

学习位运算,不禁思考一个问题,我们什么时候使用位运算呢
由于位运算比较复杂,二进制的运算不利于人去快速思考和推理,所以我们在一般的数值运算时都不会使用位运算符。但是,在某些特殊的情况下,位运算可以为我们带来一些性能提速和空间资源的节省。下面,我对在网上搜集到的一些使用场景做分析

1.快速2倍运算
利用<< >>可实现快速2倍运算,通过代码检验,位运算确实比*=更快速
在这里插入图片描述
2.交换数值
在这里插入图片描述
通过实际检验,我发现a,b=b,a比使用位运算更好
3.判断奇数和偶数
与1进行&,如果为1,那么该数为奇数;如果为0,那么该数是偶数
实际检验,发现%运算和位运算性能差不多,%更好一点
在这里插入图片描述
4.寻找列表中独一无二的数
假设一个列表中有n+1个数,其中n个数出现了2次,只有一个数出现了一次,要求找出这一个特别的数。
算法一:遍历列表,并为每个数统计出现次数,最后寻找只出现一次的数
算法二:将列表所有的数做异或运算,最后得出的结果就是特殊数
a1^a2^a3^…^a(2n+1),由于异或运算具有交换律和结合律
所以上述式子相当于(b1^b1)^(b2^b2)^…^(bn^bn)^C
由于相同数的异或为0,0和任意数的异或为该数,所以结果为C,即独一无二的数

#随机生成长列表
import random
from time import time
n=1000000
l=random.sample(range(0,n+1),n+1)
l+=l
index=random.sample(range(0,len(l)),1)
special=l[index[0]]
l.remove(special)
print("special num = ",special)
#special num =  56158

#算法一
start=time()
dic={}
result=None
for i in l:
    if dic.get(i) is not None:
        dic[i]+=1
    else:
        dic.update({i:1})
for key,value in dic.items():
    if value==1:
        result=key
print("Special num=",special,"result=",result,"time=",time()-start)
#Special num= 56158 result= 56158 time= 0.9763875007629395

#算法二
start=time()
result=0
for i in l:
    result^=i
print("Special num=",special,"result=",result,"time=",time()-start)
#Special num= 56158 result= 56158 time= 0.37100648880004883

理论上分析,两种算法的时间复杂度都是O(n),但是算法二明显快了一倍;算法一的空间复杂度为O(n)(因为创建了一个长度和n成正比的字典),而算法二却只是用了数个变量,空间复杂度为O(1)

在一些算法问题上,使用位运算可以达到很好的效果

参考

http://c.biancheng.net/view/2190.html

https://blog.csdn.net/weixin_44786530/article/details/89737903

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python中运算符的优先级是根据其在表达式中的位置来确定的。优先级高的运算符先执行,优先级低的运算符后执行。同一优先级的操作按照从左到右的顺序进行。可以使用小括号来改变运算符的优先级,括号内的运算最先执行。以下是Python中运算符的优先级从高到低的顺序:幂运算符(**),正号和负号(+、-),乘法、除法、取整除法和取余数(*、/、//、%),加法和减法(+、-),比较运算符(>、>=、<、<=、==、!=)。\[1\] 另外,Python中没有降低运算符优先级的特殊运算符。根据相对性原理,给一个运算符最高优先级,就相当于给其他运算符最低优先级了。如果想给一个运算符最低的优先级,可以将其余的运算符全部用小括号包围起来。\[2\] 总结一下,Python中的运算符优先级按照从高到低的顺序是:幂运算符(**),正号和负号(+、-),乘法、除法、取整除法和取余数(*、/、//、%),加法和减法(+、-),比较运算符(>、>=、<、<=、==、!=)。\[1\] #### 引用[.reference_title] - *1* *3* [Python中的所有运算符以及运算符的优先级](https://blog.csdn.net/2201_75641637/article/details/128449867)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Python运算符优先级](https://blog.csdn.net/gongxiaxx/article/details/125236226)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值