資料結構演算法 位運算相關知識及示例

2021-10-03 10:59:35 字數 2568 閱讀 6036

理解位運算的第一步是理解二進位制。二進位制是指數字的每一位都是1或者0。比如十進位制的2轉換成二進位制之後是10,而十進位制的10轉換成二進位制之後是1010。在程式設計師圈子中有一則流傳了很久的笑話,說世界上有10種人,一種人知道二進位制,而另一種人不知道二進位制……

其實二進位制的位運算並不是很難掌握,位運算總共只有5種運算:與/或/異或/左移/右移。如下表:

左移運算m << n 表示把 m 左移 n 位。左移 n 位的時候,將左邊的 n 位丟棄,同時在最右邊補上 n 個 0。比如:

00001010 << 2 = 0010100

10001010 << 3 = 01010000

右移運算子 m >> n 表示把 m 右移 n 位。在右移 n 位時候,最右邊的 n 位將被丟棄,同時處理左邊位的情形稍微複雜一點。如果數字是乙個無符號數值,則用 0 填補最左邊的 n 位;如果數字是乙個有符號數值,則用數字的符號位填補最左邊的 n 位。也就是說如果數字原先是乙個證書,則右移之後在最左邊補 n 個 0 ;如果數字原先是附屬,則右移之後在最左邊補 n 個1。 比如:

00001010 >> 2 = 00000010

10001010 >> 3 = 11110001

面試題:二進位制中 1 的個數

題目: 請實現乙個函式,輸入乙個整數,輸出該數二進位制表示中 1 的個數。例如: 把9表示成二進位制是1001,有2位是1。因此,如果輸入9,則該函式輸出2。

可能引起死迴圈的解法

基本思路: 先判斷整數二進位制表示中的最右邊一位是不是1:接著把輸入的整數右移一位,此時原來處於右邊數起的第二位被移到最右邊了,再判斷是不是1:這樣每次移動一位,知道整個整數變成0為止。如果乙個整數與1做與運算的結果是1,則表示該整數最右邊一位是1,否則是0。基於這種思路,**如下:

int numberof1(int n)

n = n >> 1;

}return count;

}

在上述**中,使用右移1位的運算代替除以2的操作在實際**執行時候效率更高。因為除法的效率比移位運算要低得多,在實際程式設計中應盡可能地用移位運算子代替乘除法。然而上述**確沒有考慮到負數的情況。如果輸入乙個負數,比如0x80000000,則執行時候會發生什麼情況?當把負數0x80000000右移一位的時候,並不是簡單地把最高位的1移到第二位變成0xc0000000。因為移位前是乙個負數,仍要保證移位後是乙個負數,因此移位後的最高位會設為1。如果一直做右移運算,那麼最終這個數字就會變成0xffffffff而陷入死迴圈。

常規解法

為了避免死迴圈,我們可以不右移輸入的數字 n 。首先把 n 和 1 做與運算,判斷 n 的最低位是不是1。接著把 1 左移一位得到2, 再和 n 做與運算,就能判斷 n 的次低位是不是 1 ……這樣反覆左移,每次都能判斷 n 的其中一位是不是1。基於這種思路,我們可以把**修改為:

int numberof1(int n)

flag=flag << 1;

}return count;

}

注意在這個解法中迴圈的次數等於整數二進位制的位數,32位的整數需要迴圈32次。即:迴圈終止的條件是flag為0, 在32位作業系統上,unsigned int的最大賦值是2^31=2147483647,當2^32時候超過最大值flag被賦予0。

能給面試官帶來驚喜的解法

常規解法的時間複雜度是o(32),需要迴圈32次,下面的解法只需要幾遍迴圈即可(有幾個1就迴圈幾次)。

我們先分析一下把乙個數減去1的情況。如果這個整數不等於0,那麼該整數的二進位制表示中至少有一位是1。假設這個數的最右邊一位是1,那麼減去1時,最後一位變成0而其它位都保持不變。也就是最後一位相當於做了取反操作,由1變成了0。

接下來假設最後一位不是1而是0的情況。如果該整數的二進位制表示中最右邊的1位於第m位,那麼減去1時,第m位由1變成0,而第m位之後的所有0都變成1,整數中的第m位之前的所有位都保持不變。舉個例子:乙個二進位制數1100,它的第二位時從最右邊數起的第乙個1。減去1後,第二位變成0,後面兩位0變成1,前面的1保持不變,因此得到的結果時1011。

前面的兩種情況中,我們發現把乙個整數減去1,都是把最右邊的1變成0,如果它的右邊還有0,則把所有的0都變成1,它左邊的所有位都保持不變。接下來我麼把乙個整數和它減去1的結果做位與運算,相當於把它最右邊的1變成0。還是以前面的1100為例,它減1的結果位1011。把1100和1011做位與運算,得到的結果時1000。我們把1100最右邊的1變成了0,結果剛好就是1000。

把上面的分析總結起來就是:把乙個整數減去1,再和原整數做與運算,會把該整數最右邊的1變成0。那麼乙個整數的二進位制表示中有多少個1,就可以進行多少次這樣的操作。基於這樣的思路,我們可以寫出新的**如下:

int numberof1(int n)

return count;

}

相關拓展參考資料《劍指offer第二版》

資料結構及演算法知識(二)

總結 線性結構 線性表 棧 佇列 串 陣列 廣義表 非線性結構 數 二叉樹 哈弗曼樹 帶權路徑長度最小的二叉樹。帶權路徑長度wpl 所有葉子節點的權值 路徑長度 一 並查集 並查集支援查詢乙個元素所屬的集合以及合併兩個元素各自所屬的集合等運算。在這種資料型別中,n個不同的元素被分為若干組,每組是乙個...

資料結構及演算法知識(四)

排序 內排序 在排序過程中,若整個表都是放在記憶體中處理,排序是不涉及內外存資料的交換,則稱之為內排序。外排序 若排序過程中要進行內外存資料的交換,則稱之為外排序。內排序適用於元素個數不是很多的小表,外排序適用於元素個數很多,不能一次將全部元素放入記憶體的大表。內排序有 插入排序 交換排序 選擇排序...

資料結構及演算法知識(一)

一 有序表的歸併演算法 思想 在不破壞原有表的情況下 將兩個有序表合併成乙個有序表可以採用二路歸併演算法。分別掃瞄la和lb兩個有序表,當兩個有序表都沒有掃瞄完時迴圈 比較la lb的當前元素,將其中較小的元素放入lc中,再從較小元素所在的有序表中取出下乙個元素。重複這一過程直到la或lb比較完畢,...