• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            A Za, A Za, Fighting...

            堅(jiān)信:勤能補(bǔ)拙

            [zz] 位運(yùn)算簡(jiǎn)介及實(shí)用技巧(一):基礎(chǔ)篇

            轉(zhuǎn)載:
            http://www.matrix67.com/blog/archives/263

            ------------------------------------------------------------------------------------------------------------------

            去年年底寫的關(guān)于位運(yùn)算的日志是這個(gè)Blog里少數(shù)大受歡迎的文章之一,很多人都希望我能不斷完善那篇文章。后來我看到了不少其它的資料,學(xué)習(xí)到了更多關(guān)于位運(yùn)算的知識(shí),有了重新整理位運(yùn)算技巧的想法。從今天起我就開始寫這一系列位運(yùn)算講解文章,與其說是原來那篇文章的follow-up,不如說是一個(gè)remake。當(dāng)然首先我還是從最基礎(chǔ)的東西說起。

            什么是位運(yùn)算?
                程序中的所有數(shù)在計(jì)算機(jī)內(nèi)存中都是以二進(jìn)制的形式儲(chǔ)存的。位運(yùn)算說穿了,就是直接對(duì)整數(shù)在內(nèi)存中的二進(jìn)制位進(jìn)行操作。比如,and運(yùn)算本來是一個(gè)邏輯運(yùn)算符,但整數(shù)與整數(shù)之間也可以進(jìn)行and運(yùn)算。舉個(gè)例子,6的二進(jìn)制是110,11的二進(jìn)制是1011,那么6 and 11的結(jié)果就是2,它是二進(jìn)制對(duì)應(yīng)位進(jìn)行邏輯運(yùn)算的結(jié)果(0表示False,1表示True,空位都當(dāng)0處理):
                 110
            AND 1011
            ----------
                0010  -->  2

                由于位運(yùn)算直接對(duì)內(nèi)存數(shù)據(jù)進(jìn)行操作,不需要轉(zhuǎn)成十進(jìn)制,因此處理速度非常快。當(dāng)然有人會(huì)說,這個(gè)快了有什么用,計(jì)算6 and 11沒有什么實(shí)際意義啊。這一系列的文章就將告訴你,位運(yùn)算到底可以干什么,有些什么經(jīng)典應(yīng)用,以及如何用位運(yùn)算優(yōu)化你的程序。


            Pascal和C中的位運(yùn)算符號(hào)
                下面的a和b都是整數(shù)類型,則:
            C語言  |  Pascal語言
            -------+-------------
            a & b  |  a and b
            a | b  |  a or b
            a ^ b  |  a xor b
              ~a   |   not a
            a << b |  a shl b
            a >> b |  a shr b

                注意C中的邏輯運(yùn)算和位運(yùn)算符號(hào)是不同的。520|1314=1834,但520||1314=1,因?yàn)檫壿嬤\(yùn)算時(shí)520和1314都相當(dāng)于True。同樣的,!a和~a也是有區(qū)別的。


            各種位運(yùn)算的使用
                === 1. and運(yùn)算 ===
                and運(yùn)算通常用于二進(jìn)制取位操作,例如一個(gè)數(shù) and 1的結(jié)果就是取二進(jìn)制的最末位。這可以用來判斷一個(gè)整數(shù)的奇偶,二進(jìn)制的最末位為0表示該數(shù)為偶數(shù),最末位為1表示該數(shù)為奇數(shù).

                === 2. or運(yùn)算 ===
                or運(yùn)算通常用于二進(jìn)制特定位上的無條件賦值,例如一個(gè)數(shù)or 1的結(jié)果就是把二進(jìn)制最末位強(qiáng)行變成1。如果需要把二進(jìn)制最末位變成0,對(duì)這個(gè)數(shù)or 1之后再減一就可以了,其實(shí)際意義就是把這個(gè)數(shù)強(qiáng)行變成最接近的偶數(shù)。

                === 3. xor運(yùn)算 ===
                xor運(yùn)算通常用于對(duì)二進(jìn)制的特定一位進(jìn)行取反操作,因?yàn)楫惢蚩梢赃@樣定義:0和1異或0都不變,異或1則取反。
                xor運(yùn)算的逆運(yùn)算是它本身,也就是說兩次異或同一個(gè)數(shù)最后結(jié)果不變,即(a xor b) xor b = a。xor運(yùn)算可以用于簡(jiǎn)單的加密,比如我想對(duì)我MM說1314520,但怕別人知道,于是雙方約定拿我的生日19880516作為密鑰。1314520 xor 19880516 = 20665500,我就把20665500告訴MM。MM再次計(jì)算20665500 xor 19880516的值,得到1314520,于是她就明白了我的企圖。
                下面我們看另外一個(gè)東西。定義兩個(gè)符號(hào)#和@(我怎么找不到那個(gè)圈里有個(gè)叉的字符),這兩個(gè)符號(hào)互為逆運(yùn)算,也就是說(x # y) @ y = x。現(xiàn)在依次執(zhí)行下面三條命令,結(jié)果是什么?
            x <- x # y
            y <- x @ y
            x <- x @ y

                執(zhí)行了第一句后x變成了x # y。那么第二句實(shí)質(zhì)就是y <- x # y @ y,由于#和@互為逆運(yùn)算,那么此時(shí)的y變成了原來的x。第三句中x實(shí)際上被賦值為(x # y) @ x,如果#運(yùn)算具有交換律,那么賦值后x就變成最初的y了。這三句話的結(jié)果是,x和y的位置互換了。
                加法和減法互為逆運(yùn)算,并且加法滿足交換律。把#換成+,把@換成-,我們可以寫出一個(gè)不需要臨時(shí)變量的swap過程(Pascal)。
            procedure swap(var a,b:longint);
            begin
               a:=a + b;
               b:=a - b;
               a:=a - b;
            end;

                好了,剛才不是說xor的逆運(yùn)算是它本身嗎?于是我們就有了一個(gè)看起來非常詭異的swap過程:
            procedure swap(var a,b:longint);
            begin
               a:=a xor b;
               b:=a xor b;
               a:=a xor b;
            end;


                === 4. not運(yùn)算 ===
                not運(yùn)算的定義是把內(nèi)存中的0和1全部取反。使用not運(yùn)算時(shí)要格外小心,你需要注意整數(shù)類型有沒有符號(hào)。如果not的對(duì)象是無符號(hào)整數(shù)(不能表示負(fù)數(shù)),那么得到的值就是它與該類型上界的差,因?yàn)闊o符號(hào)類型的數(shù)是用$0000到$FFFF依次表示的。下面的兩個(gè)程序(僅語言不同)均返回65435。
            var
               a:word;
            begin
               a:=100;
               a:=not a;
               writeln(a);
            end.

            #include <stdio.h>
            int main()
            {
                unsigned short a=100;
                a = ~a;
                printf( "%d\n", a );    
                return 0;
            }

                如果not的對(duì)象是有符號(hào)的整數(shù),情況就不一樣了,稍后我們會(huì)在“整數(shù)類型的儲(chǔ)存”小節(jié)中提到。

                === 5. shl運(yùn)算 ===
                a shl b就表示把a(bǔ)轉(zhuǎn)為二進(jìn)制后左移b位(在后面添b個(gè)0)。例如100的二進(jìn)制為1100100,而110010000轉(zhuǎn)成十進(jìn)制是400,那么100 shl 2 = 400。可以看出,a shl b的值實(shí)際上就是a乘以2的b次方,因?yàn)樵诙M(jìn)制數(shù)后添一個(gè)0就相當(dāng)于該數(shù)乘以2。
                通常認(rèn)為a shl 1比a * 2更快,因?yàn)榍罢呤歉讓右恍┑牟僮鳌R虼顺绦蛑谐艘?的操作請(qǐng)盡量用左移一位來代替。
                定義一些常量可能會(huì)用到shl運(yùn)算。你可以方便地用1 shl 16 - 1來表示65535。很多算法和數(shù)據(jù)結(jié)構(gòu)要求數(shù)據(jù)規(guī)模必須是2的冪,此時(shí)可以用shl來定義Max_N等常量。

                === 6. shr運(yùn)算 ===
                和shl相似,a shr b表示二進(jìn)制右移b位(去掉末b位),相當(dāng)于a除以2的b次方(取整)。我們也經(jīng)常用shr 1來代替div 2,比如二分查找、堆的插入操作等等。想辦法用shr代替除法運(yùn)算可以使程序效率大大提高。最大公約數(shù)的二進(jìn)制算法用除以2操作來代替慢得出奇的mod運(yùn)算,效率可以提高60%。


            位運(yùn)算的簡(jiǎn)單應(yīng)用
                有時(shí)我們的程序需要一個(gè)規(guī)模不大的Hash表來記錄狀態(tài)。比如,做數(shù)獨(dú)時(shí)我們需要27個(gè)Hash表來統(tǒng)計(jì)每一行、每一列和每一個(gè)小九宮格里已經(jīng)有哪些數(shù)了。此時(shí),我們可以用27個(gè)小于2^9的整數(shù)進(jìn)行記錄。例如,一個(gè)只填了2和5的小九宮格就用數(shù)字18表示(二進(jìn)制為000010010),而某一行的狀態(tài)為511則表示這一行已經(jīng)填滿。需要改變狀態(tài)時(shí)我們不需要把這個(gè)數(shù)轉(zhuǎn)成二進(jìn)制修改后再轉(zhuǎn)回去,而是直接進(jìn)行位操作。在搜索時(shí),把狀態(tài)表示成整數(shù)可以更好地進(jìn)行判重等操作。這道題是在搜索中使用位運(yùn)算加速的經(jīng)典例子。以后我們會(huì)看到更多的例子。
                下面列舉了一些常見的二進(jìn)制位的變換操作。

                功能              |           示例            |    位運(yùn)算
            ----------------------+---------------------------+--------------------
            去掉最后一位          | (101101->10110)           | x shr 1
            在最后加一個(gè)0         | (101101->1011010)         | x shl 1
            在最后加一個(gè)1         | (101101->1011011)         | x shl 1+1
            把最后一位變成1       | (101100->101101)          | x or 1
            把最后一位變成0       | (101101->101100)          | x or 1-1
            最后一位取反          | (101101->101100)          | x xor 1
            把右數(shù)第k位變成1      | (101001->101101,k=3)      | x or (1 shl (k-1))
            把右數(shù)第k位變成0      | (101101->101001,k=3)      | x and not (1 shl (k-1))
            右數(shù)第k位取反         | (101001->101101,k=3)      | x xor (1 shl (k-1))
            取末三位              | (1101101->101)            | x and 7
            取末k位               | (1101101->1101,k=5)       | x and (1 shl k-1)
            取右數(shù)第k位           | (1101101->1,k=4)          | x shr (k-1) and 1
            把末k位變成1          | (101001->101111,k=4)      | x or (1 shl k-1)
            末k位取反             | (101001->100110,k=4)      | x xor (1 shl k-1)
            把右邊連續(xù)的1變成0    | (100101111->100100000)    | x and (x+1)
            把右起第一個(gè)0變成1    | (100101111->100111111)    | x or (x+1)
            把右邊連續(xù)的0變成1    | (11011000->11011111)      | x or (x-1)
            取右邊連續(xù)的1         | (100101111->1111)         | (x xor (x+1)) shr 1
            去掉右起第一個(gè)1的左邊 | (100101000->1000)         | x and (x xor (x-1))


                最后這一個(gè)在樹狀數(shù)組中會(huì)用到。


            Pascal和C中的16進(jìn)制表示
                Pascal中需要在16進(jìn)制數(shù)前加$符號(hào)表示,C中需要在前面加0x來表示。這個(gè)以后我們會(huì)經(jīng)常用到。

            整數(shù)類型的儲(chǔ)存
                我們前面所說的位運(yùn)算都沒有涉及負(fù)數(shù),都假設(shè)這些運(yùn)算是在unsigned/word類型(只能表示正數(shù)的整型)上進(jìn)行操作。但計(jì)算機(jī)如何處理有正負(fù)符號(hào)的整數(shù)類型呢?下面兩個(gè)程序都是考察16位整數(shù)的儲(chǔ)存方式(只是語言不同)。
            var
               a,b:integer;
            begin
               a:=$0000;
               b:=$0001;
               write(a,' ',b,' ');
               a:=$FFFE;
               b:=$FFFF;
               write(a,' ',b,' ');
               a:=$7FFF;
               b:=$8000;
               writeln(a,' ',b);
            end.

            #include <stdio.h>
            int main()
            {
                short int a, b;
                a = 0x0000;
                b = 0x0001;
                printf( "%d %d ", a, b );
                a = 0xFFFE;
                b = 0xFFFF;
                printf( "%d %d ", a, b );
                a = 0x7FFF;
                b = 0x8000;
                printf( "%d %d\n", a, b );
                return 0;
            }

                兩個(gè)程序的輸出均為0 1 -2 -1 32767 -32768。其中前兩個(gè)數(shù)是內(nèi)存值最小的時(shí)候,中間兩個(gè)數(shù)則是內(nèi)存值最大的時(shí)候,最后輸出的兩個(gè)數(shù)是正數(shù)與負(fù)數(shù)的分界處。由此你可以清楚地看到計(jì)算機(jī)是如何儲(chǔ)存一個(gè)整數(shù)的:計(jì)算機(jī)用$0000到$7FFF依次表示0到32767的數(shù),剩下的$8000到$FFFF依次表示-32768到-1的數(shù)。32位有符號(hào)整數(shù)的儲(chǔ)存方式也是類似的。稍加注意你會(huì)發(fā)現(xiàn),二進(jìn)制的第一位是用來表示正負(fù)號(hào)的,0表示正,1表示負(fù)。這里有一個(gè)問題:0本來既不是正數(shù),也不是負(fù)數(shù),但它占用了$0000的位置,因此有符號(hào)的整數(shù)類型范圍中正數(shù)個(gè)數(shù)比負(fù)數(shù)少一個(gè)。對(duì)一個(gè)有符號(hào)的數(shù)進(jìn)行not運(yùn)算后,最高位的變化將導(dǎo)致正負(fù)顛倒,并且數(shù)的絕對(duì)值會(huì)差1。也就是說,not a實(shí)際上等于-a-1。這種整數(shù)儲(chǔ)存方式叫做“補(bǔ)碼”。

            posted on 2010-11-05 17:04 simplyzhao 閱讀(313) 評(píng)論(0)  編輯 收藏 引用 所屬分類: G_其他

            導(dǎo)航

            <2025年7月>
            293012345
            6789101112
            13141516171819
            20212223242526
            272829303112
            3456789

            統(tǒng)計(jì)

            常用鏈接

            留言簿(1)

            隨筆分類

            隨筆檔案

            搜索

            最新評(píng)論

            閱讀排行榜

            評(píng)論排行榜

            久久亚洲2019中文字幕| 国产精品久久久久无码av| 婷婷久久综合九色综合98| 色天使久久综合网天天| 手机看片久久高清国产日韩| 久久久久久久尹人综合网亚洲| 国产成人精品综合久久久久| 97久久久久人妻精品专区| 久久亚洲欧美国产精品| 99久久精品免费| 丁香五月综合久久激情| 东方aⅴ免费观看久久av| 国产精品一区二区久久精品涩爱| 久久97精品久久久久久久不卡| 久久久久亚洲精品无码网址 | 亚洲狠狠久久综合一区77777| 国产精品久久久久久福利漫画| 久久精品国产亚洲麻豆| 久久99亚洲综合精品首页| 久久久久久夜精品精品免费啦| 欧美激情精品久久久久久| 99久久香蕉国产线看观香| 亚洲中文字幕无码久久2020| 久久这里只有精品18| AA级片免费看视频久久| 青青草国产97免久久费观看| 久久亚洲精品人成综合网 | 久久免费美女视频| 久久一区二区三区免费| 亚洲欧洲日产国码无码久久99| 一本综合久久国产二区| 久久久久亚洲精品男人的天堂| 思思久久99热只有频精品66| 国产精品福利一区二区久久| 久久久WWW成人免费毛片| 国产免费久久精品丫丫| 麻豆久久久9性大片| 久久久精品国产| 精品久久香蕉国产线看观看亚洲| 四虎久久影院| 国内精品久久久久久久涩爱|