📄 位运算简介及实用技巧(二):进阶篇(1).htm
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3c.org/TR/1999/REC-html401-19991224/loose.dtd">
<!-- saved from url=(0047)http://www.matrix67.com/blog/article.asp?id=312 -->
<HTML lang=UTF-8 xmlns="http://www.w3.org/1999/xhtml"><HEAD><TITLE>位运算简介及实用技巧(二):进阶篇(1) - Matrix67: My Blog</TITLE>
<META http-equiv=Content-Type content="text/html; charset=UTF-8">
<META http-equiv=Content-Language content=UTF-8>
<META content=all name=robots>
<META content=matrix67@tom.com,Matrix67 name=author>
<META content="PJBlog2 CopyRight 2005" name=Copyright>
<META
content=matrix67,PJBlog,blog,博客,OI,OIer,NOI,NOIp,Pascal,信息学,算法,数据结构,数学,趣题,美剧,电影
name=keywords>
<META
content="Matrix67: My Blog - 50% Informatics, 50% Mathematics, and 50% Imagination"
name=description><LINK
title="订阅 Matrix67: My Blog - Program Impossible 所有文章(rss2)"
href="http://www.matrix67.com/blog/feed.asp?cateID=6" type=application/rss+xml
rel=alternate><LINK title="订阅 Matrix67: My Blog - Program Impossible 所有文章(atom)"
href="http://www.matrix67.com/blog/atom.asp?cateID=6" type=application/atom+xml
rel=alternate><LINK rev=stylesheet media=all
href="位运算简介及实用技巧(二):进阶篇(1).files/global.css" type=text/css rel=stylesheet><!--全局样式表--><LINK rev=stylesheet media=all
href="位运算简介及实用技巧(二):进阶篇(1).files/layout.css" type=text/css rel=stylesheet><!--层次样式表--><LINK rev=stylesheet media=all
href="位运算简介及实用技巧(二):进阶篇(1).files/typography.css" type=text/css rel=stylesheet><!--局部样式表--><LINK rev=stylesheet media=all
href="位运算简介及实用技巧(二):进阶篇(1).files/link.css" type=text/css rel=stylesheet><!--超链接样式表--><LINK rev=stylesheet media=all
href="位运算简介及实用技巧(二):进阶篇(1).files/editor.css" type=text/css rel=stylesheet><!--UBB编辑器代码--><LINK rev=stylesheet media=all
href="位运算简介及实用技巧(二):进阶篇(1).files/highlight.css" type=text/css
rel=stylesheet><LINK rev=stylesheet media=all
href="位运算简介及实用技巧(二):进阶篇(1).files/bt.css" type=text/css rel=stylesheet><LINK
href="favicon.ico" type=image/x-icon rel=icon><LINK href="favicon.ico"
type=image/x-icon rel="shortcut icon">
<SCRIPT src="位运算简介及实用技巧(二):进阶篇(1).files/common.js"
type=text/javascript></SCRIPT>
<SCRIPT src="位运算简介及实用技巧(二):进阶篇(1).files/BubbleTooltips.js"
type=text/javascript></SCRIPT>
<META content="MSHTML 6.00.2900.3268" name=GENERATOR></HEAD>
<BODY onkeydown=PressKey() onload=initJS()><A accessKey=i
href="http://www.matrix67.com/blog/default.asp"></A><A accessKey=z
href="javascript:history.go(-1)"></A>
<DIV id=container><!--顶部-->
<DIV id=header>
<DIV id=blogname>Matrix67: My Blog
<DIV id=blogTitle>50% Informatics, 50% Mathematics, and 50%
Imagination</DIV></DIV>
<DIV id=menu>
<DIV id=Left></DIV>
<DIV id=Right></DIV>
<UL>
<LI class=menuL></LI>
<LI><A class=menuA title=日志首页
href="http://www.matrix67.com/blog/default.asp">Index</A></LI>
<LI class=menuDiv></LI>
<LI><A class=menuA title=标签云集
href="http://www.matrix67.com/blog/tag.asp">TagsCloud</A></LI>
<LI class=menuDiv></LI>
<LI><A class=menuA title=个人档案
href="http://www.matrix67.com/blog/LoadMod.asp?plugins=AboutMeForPJBlog">Profile</A></LI>
<LI class=menuDiv></LI>
<LI><A class=menuA title=留言板
href="http://www.matrix67.com/blog/LoadMod.asp?plugins=GuestBookForPJBlog">GuestBook</A></LI>
<LI class=menuDiv></LI>
<LI><A class=menuA title=讨论区
href="http://www.matrix67.com/blog/discuss.asp?action=index">Discuss(beta)</A></LI>
<LI class=menuR></LI></UL></DIV></DIV><!--内容-->
<DIV id=Tbody>
<DIV id=mainContent>
<DIV id=innermainContent>
<DIV id=mainContent-topimg></DIV>
<DIV class=content-width id=Content_ContentList><A accessKey=B
href="http://www.matrix67.com/blog/article.asp?id=312#body" name=body></A>
<DIV class=pageContent>
<DIV style="FLOAT: right; WIDTH: auto"><A title=订阅所有日志 accessKey=F
href="http://www.matrix67.com/blog/feed.asp?cateID=6" target=_blank><IMG
style="MARGIN-BOTTOM: -3px" alt=订阅所有日志 src="位运算简介及实用技巧(二):进阶篇(1).files/rss.png"
border=0> 订阅</A> | <A title="上一篇日志: 位运算简介及实用技巧(一):基础篇" accessKey=,
href="http://www.matrix67.com/blog/article.asp?id=311"><IMG alt=""
src="位运算简介及实用技巧(二):进阶篇(1).files/Cprevious.gif" border=0>上一篇</A> | <A
title="下一篇日志: 第300篇日志:祝福所有NOI选手" accessKey=.
href="http://www.matrix67.com/blog/article.asp?id=313"><IMG alt=""
src="位运算简介及实用技巧(二):进阶篇(1).files/Cnext.gif" border=0>下一篇</A> </DIV><IMG
style="MARGIN: 0px 2px -4px 0px" alt="" src="位运算简介及实用技巧(二):进阶篇(1).files/24.gif">
<STRONG><A title="查看所有Program Impossible的日志"
href="http://www.matrix67.com/blog/default.asp?cateID=6">Program
Impossible</A></STRONG> </DIV>
<DIV class=Content>
<DIV class=Content-top>
<DIV class=ContentLeft></DIV>
<DIV class=ContentRight></DIV>
<H1 class=ContentTitle><STRONG>位运算简介及实用技巧(二):进阶篇(1)</STRONG></H1>
<H2 class=ContentAuthor>作者:matrix67 日期:2007-07-24</H2></DIV>
<DIV class=Content-Info>
<DIV class=InfoOther>字体大小: <A accessKey=1
href="javascript:SetFont('12px')">小</A> <A accessKey=2
href="javascript:SetFont('14px')">中</A> <A accessKey=3
href="javascript:SetFont('16px')">大</A></DIV>
<DIV class=InfoAuthor><IMG style="MARGIN: 0px 2px -6px 0px" alt=""
src="位运算简介及实用技巧(二):进阶篇(1).files/hn2_sunny.gif"><IMG alt=""
src="位运算简介及实用技巧(二):进阶篇(1).files/hn2_t_sunny.gif"> <IMG
style="MARGIN: 0px 2px -1px 0px" alt=""
src="位运算简介及实用技巧(二):进阶篇(1).files/level5.gif"> | 本文内容遵从<A
href="http://creativecommons.org/licenses/by-nc-sa/3.0/deed.zh"
target=_blank>CC版权协议</A> 转载请注明出自matrix67.com </DIV></DIV>
<DIV class=Content-body id=logPanel>===== 真正强的东西来了!
=====<BR><BR><STRONG>二进制中的1有奇数个还是偶数个</STRONG><BR> 我们可以用下面的代码来计算一个32位整数的二进制中1的个数的奇偶性,当输入数据的二进制表示里有偶数个数字1时程序输出0,有奇数个则输出1。例如,1314520的二进制101000000111011011000中有9个1,则x=1314520时程序输出1。<BR>
<DIV class=UBBPanel>
<DIV class=UBBTitle><IMG style="MARGIN: 0px 2px -3px 0px" alt=程序代码
src="位运算简介及实用技巧(二):进阶篇(1).files/quote.gif"> 程序代码</DIV>
<DIV class=UBBContent><PRE><CODE class=pascal>var<BR> i,x,c:longint;<BR>begin<BR> readln(x);<BR> c:=0;<BR> for i:=1 to 32 do<BR> begin<BR> c:=c + x and 1;<BR> x:=x shr 1;<BR> end;<BR> writeln( c and 1 );<BR>end.</CODE></PRE></DIV></DIV><BR> 但这样的效率并不高,位运算的神奇之处还没有体现出来。<BR> 同样是判断二进制中1的个数的奇偶性,下面这段代码就强了。你能看出这个代码的原理吗?<BR>
<DIV class=UBBPanel>
<DIV class=UBBTitle><IMG style="MARGIN: 0px 2px -3px 0px" alt=程序代码
src="位运算简介及实用技巧(二):进阶篇(1).files/quote.gif"> 程序代码</DIV>
<DIV class=UBBContent><PRE><CODE class=pascal>var<BR> x:longint;<BR>begin<BR> readln(x);<BR> x:=x xor (x shr 1);<BR> x:=x xor (x shr 2);<BR> x:=x xor (x shr 4);<BR> x:=x xor (x shr 8);<BR> x:=x xor (x shr 16);<BR> writeln(x and 1);<BR>end.</CODE></PRE></DIV></DIV><BR> 为了说明上面这段代码的原理,我们还是拿1314520出来说事。1314520的二进制为101000000111011011000,第一次异或操作的结果如下:<BR><BR><SPAN
style="FONT-FAMILY: 宋体"> 00000000000101000000111011011000<BR>XOR 0000000000010100000011101101100<BR>---------------------------------------<BR> 00000000000111100000100110110100</SPAN><BR><BR> 得到的结果是一个新的二进制数,其中右起第i位上的数表示原数中第i和i+1位上有奇数个1还是偶数个1。比如,最右边那个0表示原数末两位有偶数个1,右起第3位上的1就表示原数的这个位置和前一个位置中有奇数个1。对这个数进行第二次异或的结果如下:<BR><BR><SPAN
style="FONT-FAMILY: 宋体"> 00000000000111100000100110110100<BR>XOR
000000000001111000001001101101<BR>---------------------------------------<BR> 00000000000110011000101111011001</SPAN><BR><BR> 结果里的每个1表示原数的该位置及其前面三个位置中共有奇数个1,每个0就表示原数对应的四个位置上共偶数个1。一直做到第五次异或结束后,得到的二进制数的最末位就表示整个32位数里有多少个1,这就是我们最终想要的答案。<BR><BR><BR><STRONG>计算二进制中的1的个数</STRONG><BR> 同样假设x是一个32位整数。经过下面五次赋值后,x的值就是原数的二进制表示中数字1的个数。比如,初始时x为1314520(网友抓狂:能不能换一个数啊),那么最后x就变成了9,它表示1314520的二进制中有9个1。<BR>
<DIV class=UBBPanel>
<DIV class=UBBTitle><IMG style="MARGIN: 0px 2px -3px 0px" alt=程序代码
src="位运算简介及实用技巧(二):进阶篇(1).files/quote.gif"> 程序代码</DIV>
<DIV class=UBBContent><PRE><CODE class=pascal>x := (x and $55555555) + ((x shr 1) and $55555555); <BR>x := (x and $33333333) + ((x shr 2) and $33333333); <BR>x := (x and $0F0F0F0F) + ((x shr 4) and $0F0F0F0F); <BR>x := (x and $00FF00FF) + ((x shr 8) and $00FF00FF); <BR>x := (x and $0000FFFF) + ((x shr 16) and $0000FFFF); </CODE></PRE></DIV></DIV><BR> 为了便于解说,我们下面仅说明这个程序是如何对一个8位整数进行处理的。我们拿数字211(我们班某MM的生日)来开刀。211的二进制为11010011。<BR><BR><SPAN
style="FONT-FAMILY: 宋体">+---+---+---+---+---+---+---+---+<BR>| 1 | 1 | 0 | 1 | 0
| 0 | 1 | 1 |
<---原数<BR>+---+---+---+---+---+---+---+---+<BR>| 1
0 | 0 1 | 0
0 | 1 0 |
<---第一次运算后<BR>+-------+-------+-------+-------+<BR>| 0
0 1 1 | 0 0 1
0 |
<---第二次运算后<BR>+---------------+---------------+<BR>| 0
0 0 0 0 1 0 1 |
<---第三次运算后,得数为5<BR>+-------------------------------+</SPAN><BR><BR> 整个程序是一个分治的思想。第一次我们把每相邻的两位加起来,得到每两位里1的个数,比如前两位10就表示原数的前两位有2个1。第二次我们继续两两相加,10+01=11,00+10=10,得到的结果是00110010,它表示原数前4位有3个1,末4位有2个1。最后一次我们把0011和0010加起来,得到的就是整个二进制中1的个数。程序中巧妙地使用取位和右移,比如第二行中$33333333的二进制为00110011001100....,用它和x做and运算就相当于以2为单位间隔取数。shr的作用就是让加法运算的相同数位对齐。<BR><BR><BR><STRONG>二分查找32位整数的前导0个数</STRONG><BR> 这里用的C语言,我直接Copy的Hacker's
Delight上的代码。这段代码写成C要好看些,写成Pascal的话会出现很多begin和end,搞得代码很难看。程序思想是二分查找,应该很简单,我就不细说了。<BR>
<DIV class=UBBPanel>
<DIV class=UBBTitle><IMG style="MARGIN: 0px 2px -3px 0px" alt=程序代码
src="位运算简介及实用技巧(二):进阶篇(1).files/quote.gif"> 程序代码</DIV>
<DIV class=UBBContent><PRE><CODE class=cpp>int nlz(unsigned x)<BR>{<BR> int n;<BR><BR> if (x == 0) return(32);<BR> n = 1;<BR> if ((x >> 16) == 0) {n = n +16; x = x <<16;}<BR> if ((x >> 24) == 0) {n = n + 8; x = x << 8;}<BR> if ((x >> 28) == 0) {n = n + 4; x = x << 4;}<BR> if ((x >> 30) == 0) {n = n + 2; x = x << 2;}<BR> n = n - (x >> 31);<BR> return n;<BR>}</CODE></PRE></DIV></DIV><BR><BR><BR><STRONG>只用位运算来取绝对值</STRONG><BR> 这是一个非常有趣的问题。大家先自己想想吧,Ctrl+A显示答案。<BR> <SPAN
style="COLOR: #f3f3f3">答案:假设x为32位整数,则x xor (not (x shr 31) + 1) + x shr
31的结果是x的绝对值<BR> x shr
31是二进制的最高位,它用来表示x的符号。如果它为0(x为正),则not (x shr 31) +
1等于$00000000,异或任何数结果都不变;如果最高位为1(x为负),则not (x shr 31) +
1等于$FFFFFFFF,x异或它相当于所有数位取反,异或完后再加一。</SPAN><BR><BR><BR><STRONG>高低位交换</STRONG><BR> <A
href="http://www.vijos.cn/Problem_Show.asp?id=1201"
target=_blank>这个题</A>实际上是我出的,做为学校内部NOIp模拟赛的第一题。题目是这样:<BR>
<DIV class=UBBPanel>
<DIV class=UBBTitle><IMG style="MARGIN: 0px 2px -3px 0px" alt=引用内容
src="位运算简介及实用技巧(二):进阶篇(1).files/quote.gif"> 引用内容</DIV>
<DIV
class=UBBContent> 给出一个小于2^32的正整数。这个数可以用一个32位的二进制数表示(不足32位用0补足)。我们称这个二进制数的前16位为“高位”,后16位为“低位”。将它的高低位交换,我们可以得到一个新的数。试问这个新的数是多少(用十进制表示)。<BR> 例如,数1314520用二进制表示为0000
0000 0001 0100 0000 1110 1101 1000(添加了11个前导0补足为32位),其中前16位为高位,即0000 0000 0001
0100;后16位为低位,即0000 1110 1101 1000。将它的高低位进行交换,我们得到了一个新的二进制数0000 1110 1101 1000
0000 0000 0001
0100。它即是十进制的249036820。</DIV></DIV><BR> 当时几乎没有人想到用一句位操作来代替冗长的程序。使用位运算的话两句话就完了。<BR>
<DIV class=UBBPanel>
<DIV class=UBBTitle><IMG style="MARGIN: 0px 2px -3px 0px" alt=程序代码
src="位运算简介及实用技巧(二):进阶篇(1).files/quote.gif"> 程序代码</DIV>
<DIV class=UBBContent><PRE><CODE class=pascal>var<BR> n:dword;<BR>begin<BR> readln( n );<BR> writeln( (n shr 16) or (n shl 16) );<BR>end.</CODE></PRE></DIV></DIV><BR> 而事实上,Pascal有一个系统函数swap直接就可以用。<BR><BR><BR><STRONG>二进制逆序</STRONG><BR> 下面的程序读入一个32位整数并输出它的二进制倒序后所表示的数。<BR> <SPAN
style="FONT-FAMILY: 宋体">输入:
1314520 (二进制为00000000000101000000111011011000)</SPAN><BR> <SPAN
style="FONT-FAMILY: 宋体">输出:
460335104 (二进制为00011011011100000010100000000000)</SPAN><BR>
<DIV class=UBBPanel>
<DIV class=UBBTitle><IMG style="MARGIN: 0px 2px -3px 0px" alt=程序代码
src="位运算简介及实用技巧(二):进阶篇(1).files/quote.gif"> 程序代码</DIV>
<DIV class=UBBContent><PRE><CODE class=pascal>var<BR> x:dword;<BR>begin<BR> readln(x);<BR> x := (x and $55555555) shl 1 or (x and $AAAAAAAA) shr 1;<BR> x := (x and $33333333) shl 2 or (x and $CCCCCCCC) shr 2;<BR> x := (x and $0F0F0F0F) shl 4 or (x and $F0F0F0F0) shr 4;<BR> x := (x and $00FF00FF) shl 8 or (x and $FF00FF00) shr 8;<BR> x := (x and $0000FFFF) shl 16 or (x and $FFFF0000) shr 16;<BR> writeln(x);<BR>end.</CODE></PRE></DIV></DIV><BR> 它的原理和刚才求二进制中1的个数那个例题是大致相同的。程序首先交换每相邻两位上的数,以后把互相交换过的数看成一个整体,继续进行以2位为单位、以4位为单位的左右对换操作。我们再次用8位整数211来演示程序执行过程:<BR><SPAN
style="FONT-FAMILY: 宋体">+---+---+---+---+---+---+---+---+<BR>| 1 | 1 | 0 | 1 | 0
| 0 | 1 | 1 |
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -