前端玩轉位運算(N皇后+Vue3位運算應用)

觀感度:🌟🌟🌟🌟🌟前端

口味:東北小炒肉vue

烹飪時間:10mingit

本文已收錄在前端食堂同名倉庫Github github.com/Geekhyt,歡迎光臨食堂,若是以爲酒菜還算可口,賞個 Star 對食堂老闆來講是莫大的鼓勵。

初識位運算

記憶

  • & ,與 兩個位都爲 1 時,結果才爲 1
  • | ,或 兩個位都爲 0 時,結果才爲 0
  • ^ ,異或 兩個位相同爲 0 ,相異爲 1
  • ~,按位取反 全部 0 變 1,1 變 0
  • <<,左移 各二進位所有左移若干位,高位丟棄,低位補 0
  • >>,右移 各二進位所有右移若干位,對無符號數,高位補 0 ,有符號數,各編譯器處理方法不同,有的補符號位,有的補 0

理解

其實很簡單,小學數學題難度,花幾分鐘看完若是看懂了請點個讚唄。github

左移

二進制左移一位,就是將數字翻倍。面試

二進制 110101 向左移一位,就是在末尾添加一位 0,也就是 1101010。(此處討論的是數字沒有溢出的狀況)算法

二進制 110101 轉化成十進制:數組

1 2^5 + 1 2^4 + 0 2^3 + 1 2^2 + 0 2^1 + 1 2^0

= 32 + 16 + 0 + 4 + 0 + 1 數據結構

= 53函數

二進制 1101010 轉化成十進制:post

1 2^6 + 1 2^5 + 0 2^4 + 1 2^3 + 0 2^2 + 1 2^1 + 0 * 2^0

= 64 + 32 + 0 + 8 + 0 + 2 + 0

= 106

右移

二進制右移一位,就是將數字除以 2 並求整數商。

二進制 110101 向右移一位,就是去除掉末尾的那一位,也就是 11010

二進制 11010 轉化成十進制:

1 2^4 + 1 2^3 + 0 2^2 + 1 2^1 + 0 * 2^0

= 16 + 8 + 0 + 2 + 0

= 26

無符號右移和有符號右移 (邏輯右移和算術右移)

無符號右移使用 >>> 表示,而有符號右移使用 >> 表示。

>>> 無符號右移 1 位,右邊丟棄,左邊補 0 便可。

>> 有符號右移保留符號,拷貝最左側的位來填充左側,向右位移並丟棄最右邊的位。

因爲左移位無需考慮高位補 1 仍是補 0(符號位可能爲 1 或 0),因此不須要區分無符號左移和有符號左移。

位的或

參與操做的位中只要有一個位是 1, 那麼最終結果就是 1。

若是咱們將 110101100011 進行按位的或操做,就會獲得 110111

位的與

參與操做的位中必須都是 1,最終結果纔是 1,不然爲 0。

若是咱們將 110101100011 進行按位的與操做,就會獲得 100001

位的異或

參與操做的位相同,最終結果是 0 ,不然爲 1。

想要獲得 1,參與操做的兩個位必須不相同,也就是異或中「異」的含義。

若是咱們將 110101100011 進行按位的異或操做,就會獲得 10110

經常使用公式

判斷奇偶

x % 2 === 1 -> (x & 1) === 1 (奇數)

x % 2 === 0 -> (x & 1) === 0 (偶數)

x >> 1 -> x / 2

即:x = x / 2; -> x = x >> 1;

mid = (left + right) / 2; -> mid = (left + right) >> 1;

x = x & (x - 1)

清零最低位的 1,表明將最後一位 1 變成 0。

x & -x

獲得最低位的 1,表明除最後一位 1 保留,其餘位所有爲 0。

將 x 最右邊的 n 位清零

x & (~0 << n)

獲取 x 的第 n 位值

(x >> n) & 1

獲取 x 的第 n 位的冪值

x & (1 << (n - 1))

僅將第 n 位置爲 1

x | (1 << n)

僅將第 n 位置爲 0

x & (~(1 << n))

將 x 最高位至第 n 位(含)清零

x & ((1 << n) - 1)

將第 n 位至第 0 位(含)清零

x & (~((1 << (n + 1)) - 1))

Leecode真題解析 N皇后II

n 皇后問題研究的是如何將 n 個皇后放置在 n×n 的棋盤上,而且使皇后彼此之間不能相互攻擊。

(圖片來源LeeCode,同上原題連接)
上圖爲 8 皇后問題的一種解法。

給定一個整數 n,返回 n 皇后不一樣的解決方案的數量。

示例:

輸入: 4
輸出: 2
解釋: 4 皇后問題存在以下兩個不一樣的解法。
[
 [".Q..",  // 解法 1
  "...Q",
  "Q...",
  "..Q."],

 ["..Q.",  // 解法 2
  "Q...",
  "...Q",
  ".Q.."]
]

提示:

  • 皇后,是國際象棋中的棋子,意味着國王的妻子。皇后只作一件事,那就是「吃子」。當她碰見能夠吃的棋子時,就迅速衝上去吃掉棋子。固然,她橫、豎、斜均可走一或 N-1 步,可進可退。(引用自 百度百科 - 皇后 )

理解

皇后能夠橫、直、斜走,格數不限。題目要求皇后彼此之間不能相互攻擊,也就是說須要知足任意兩個皇后不能在同一行、同一列以及同一條斜線上。

熟悉這道題的同窗,能夠看出最直觀的作法是利用回溯法進行求解。

遍歷枚舉出全部可能的選擇,依次在每一行放置一個皇后,每次新放置的皇后不能和已經放置的皇后之間存在攻擊。

爲了下降時間複雜度,最理想的狀況是在 O(1) 的時間內判斷該位置所在的幾條線上是否已經有皇后,能夠利用集合來進行位置判斷。

爲了讓你更好的理解,我利用回溯法將 4 皇后可能的解法畫了出來。以下圖所示:

一句話理解四種算法思想
  • 分治:分而治之,先解決子問題,再將子問題的解合併求出原問題。
  • 貪心:一條路走到黑,選擇當下局部最優的路線,沒有後悔藥。
  • 回溯:一條路走到黑,手握後悔藥,能夠無數次重來。(英雄聯盟艾克大招無冷卻)。
  • 動態規劃:上帝視角,手握無數平行宇宙的歷史存檔,同時發展出無數個將來。

下面這張圖是兩條對角線方向的斜線的規律,聰明的你確定一眼就能看出來:

解法一 集合回溯

以下所示是官方給出的題解:

const backtrack = (n, row, columns, diagonals1, diagonals2) => {
    if (row === n) {
        return 1;
    } else {
        let count = 0;
        for (let i = 0; i < n; i++) {
            if (columns.has(i)) {
                continue;
            }
            const diagonal1 = row - i;
            if (diagonals1.has(diagonal1)) {
                continue;
            }
            const diagonal2 = row + i;
            if (diagonals2.has(diagonal2)) {
                continue;
            }
            columns.add(i);
            diagonals1.add(diagonal1);
            diagonals2.add(diagonal2);
            count += backtrack(n, row + 1, columns, diagonals1, diagonals2);
            columns.delete(i);
            diagonals1.delete(diagonal1);
            diagonals2.delete(diagonal2);
        }
        return count;
    }
}
const totalNQueens = function(n) {
    const columns = new Set();
    const diagonals1 = new Set();
    const diagonals2 = new Set();
    return backtrack(n, 0, columns, diagonals1, diagonals2);
};
  • 時間複雜度:O(N!)
  • 空間複雜度:O(N)

解法二 位運算回溯

學會了位運算,你能夠將代碼寫的更加優雅。

先來明確幾個概念和須要用到的公式:

  • n:n層
  • row:當前層
  • cols:列
  • pie:撇,左斜線(副對角線)
  • na:捺,右斜線(正對角線)
  • 二進制爲 1,表明不可放置,0 相反
  • x & -x :獲得最低位的1 (表明除最後一位 1 保留,其餘位所有爲 0)
  • x & (x - 1):清零最低位的 1 (表明將最後一位 1 變成 0)
  • x & ((1 << n) - 1):將 x 的最高位至第 n 位(含)清零
思路

將 N 個位置對應成 N 個二進制位,0 表明能夠選擇,1 表明不能選擇。好比八皇后當前第一行的第二位被選擇時的狀態是 00100000,那麼下一行的第二位也不能被選擇,正對角線(na)對應的第三位不能被選擇(對應當前行右移了一位),狀態表示爲:00100000。副對角線(pie)對應的第一位不能被選擇(對應當前行左移了一位),狀態表示爲 10000000。

const totalNQueens = function(n) {
    let res = 0;
    const dfs = (n, row, cols, pie, na) => {
        if (row >= n) {
            res++;
            return;
        }
        let bits = (~(cols | pie | na)) & ((1 << n) - 1) // 1
        while (bits) { // 2
            let p = bits & -bits // 3
            dfs(n, row + 1, cols | p, (pie | p) << 1, (na | p) >> 1) // 4
            bits = bits & (bits - 1) // 5
        }
    }
    dfs(n, 0, 0, 0, 0);
    return res;
};
  • 時間複雜度:O(N!)
  • 空間複雜度:O(N)
代碼解讀

1.cols | pie | na 表示全部可以被皇后攻擊的格子,~(cols | pie | na)取反表示將沒有被佔的格子從 0 變爲 1,以便後續的位遍歷。這裏用到公式:x & ((1 << n) - 1):將 x 的最高位至第 n 位(含)清零。一個 int 的二進制位至少有 32 位,咱們將前面不須要的位置清零。因此,這行代碼表示獲得當前全部的空位,也就是能夠放置皇后的格子。

2.只要 bits 中有 1,就說明還有格子能夠放置皇后,每次遍歷都會將其清零(表示在p位置放入了皇后),也就是註釋 5 的代碼含義。對應公式:x & (x - 1):清零最低位的 1 (表明將最後一位 1 變成 0)。

3.對應公式:x & -x :獲得最低位的1 (表明除最後一位 1 保留,其餘位所有爲 0),表示當前皇后可放入的位置。

4.修改狀態,進入下一層遞歸。row + 1 表明搜索下一行,cols | p 表明目前全部能夠放置皇后的列。(pie | p) << 1(na | p) >> 1,在上面思路中已經說過了,再也不贅述。

Vue3 中的位運算之 shapeFlags、patchFlags

Vue3 中也有一些關於位運算的實踐。

shapeFlags

shapeFlags 針對 VNode 的 type 進行了更詳細的分類,便於在 patch 階段,根據不一樣的類型執行相應的邏輯。

能夠點擊此處跳轉到源碼倉庫進行查看

// packages/shared/src/shapeFlags.ts
export const enum ShapeFlags {
  ELEMENT = 1, // HTML 或 SVG 標籤 普通 DOM 元素
  FUNCTIONAL_COMPONENT = 1 << 1, // 函數式組件
  STATEFUL_COMPONENT = 1 << 2, // 普通有狀態組件
  TEXT_CHILDREN = 1 << 3, // 子節點是純文本
  ARRAY_CHILDREN = 1 << 4, // 子節點是數組
  SLOTS_CHILDREN = 1 << 5, // 子節點是插槽
  TELEPORT = 1 << 6, // Teleport
  SUSPENSE = 1 << 7, // Suspense
  COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8, // 須要被 keep-alive 的有狀態組件
  COMPONENT_KEPT_ALIVE = 1 << 9, // 已經被 keep-alive 的有狀態組件
  COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT // 有狀態組件和函數組件都是組件,用 COMPONENT 表示
}

patchFlags

patchFlags 用於標識節點更新的類型,用於運行時優化。

// packages/shared/src/patchFlags.ts
export const enum PatchFlags {
  TEXT = 1, // 動態文本節點
  CLASS = 1 << 1, // 動態 class
  STYLE = 1 << 2, // 動態 style
  PROPS = 1 << 3, // 動態屬性
  FULL_PROPS = 1 << 4, // 具備動態 key 屬性,當 key 改變時,須要進行完整的 diff 比較
  HYDRATE_EVENTS = 1 << 5, // 具備監聽事件的節點
  STABLE_FRAGMENT = 1 << 6, // 子節點順序不會被改變的 fragment
  KEYED_FRAGMENT = 1 << 7, // 帶有 key 屬或部分子節點有 key 的 fragment
  UNKEYED_FRAGMENT = 1 << 8, // 子節點沒有 key 的 fragment
  NEED_PATCH = 1 << 9, // 非 props 的比較,好比 ref 或指令
  DYNAMIC_SLOTS = 1 << 10, // 動態插槽
  DEV_ROOT_FRAGMENT = 1 << 11, // 僅供開發時使用,表示將註釋放在模板根級別的片斷
  HOISTED = -1, // 靜態節點
  BAIL = -2 // diff 算法要退出優化模式
}

經過進行 | 或運算進行標記的組合,若是當前節點是一個動態文本節點(0000 0001),它同時又具備動態 style (0000 0100),兩者進行 | 或運算後值爲 (0000 0101)。

經過進行 & 與運算進行標記的檢查。能夠點擊此處跳轉到源碼倉庫進行查看

讀這部分註釋的時候發現了引用文件路徑的錯誤,提交了Pr,成功混入了 Vue Contributor,與尤大進行了一波親密互動。

因此說,好好學習是有回報的,一塊兒加油吧,打工人!

image

若是你對 Vue3 DOM Diff 核心算法感興趣的話,也歡迎閱讀個人另一篇專欄,Vue3 DOM Diff 核心算法解析

更有其餘算法系列專欄,讓你一次看過癮:

❤️愛心三連擊

1.若是你以爲食堂酒菜還合胃口,就點個贊支持下吧,你的是我最大的動力。

2.關注公衆號前端食堂,吃好每一頓飯!

3.點贊、評論、轉發 === 催更!

相關文章
相關標籤/搜索