數據結構與算法【B樹】的Java實現+圖解

目錄

B樹

特性

實現

節點準備

大體框架

實現分裂

實現新增

實現刪除

完整代碼


B樹

也是一種自平衡的樹形數據結構,主要用于管理磁盤上的數據管理(減少磁盤IO次數)。而之前說的AVL樹與紅黑樹適合用于內存數據管理。存儲一個100w的數據使用AVL存儲,樹高大約為20層(log_2100W),如果使用磁盤IO查詢20次效率較低。

特性

度degree:指樹中節點孩子數

階order:指所有節點孩子數中最大值

一棵 B-樹具有以下性質

特性1:每個節點 x 具有

  • 屬性 n,表示節點 x 中 key 的個數
  • 屬性 leaf,表示節點是否是葉子節點
  • 節點 key 可以有多個,以升序存儲

特性2:每個節點最多具有m個孩子,其中m叫做B-樹的階

特性3:除根結點與葉子節點外,每個節點至少有ceil(m/2)個孩子,根節點不是葉子節點時,最少有兩個孩子。葉子節點沒有孩子

特性2:每個非葉子節點中的孩子數是 n + 1。而n的取值為ceil(m/2)-1<=n<=m-1。

特性3:最小度數t(節點的孩子數稱為度)和節點中鍵數量的關系如下:

最小度數t鍵數量范圍
21 ~ 3
32 ~ 5
43 ~ 7
......
n(n-1) ~ (2n-1)

其中,當節點中鍵數量達到其最大值時,即 3、5、7 ... 2n-1,需要分裂

特性4:葉子節點的深度都相同

實現

節點準備

B樹的節點屬性,與其他樹不太相同,首先是key可以有多個,因此要設置為數組,孩子節點也未知,因此也要設置為數組。本應該還存在一個value屬性,這里簡化掉,不添加該屬性。

static class Node {boolean leaf = true; //是否是葉子節點int keyNumber; //有效keyint t; //最小度int[] keys; // keys數組Node[] children; //孩子節點數組public Node(int t) {this.t = t;this.keys = new int[2 * t - 1];this.children = new Node[2 * t];//最大孩子節點個數為為2*t}@Overridepublic String toString() {return Arrays.toString(Arrays.copyOfRange(keys, 0, keyNumber));}/*** 根據key獲取對應節點** @param key* @return*/Node get(int key) {int i = 0;while (i < keyNumber) {//如果在該節點找到,那么直接返回即可if (keys[i] == key) {return this;}//說明要找的元素可能在children[i]中if (keys[i] > key) {break;}i++;}//如果是葉子節點,直接返回nullif (leaf) {return null;}return children[i].get(key);}/*** 指定位置插入元素** @param key* @param index*/void insertKey(int key, int index) {System.arraycopy(keys, index, keys, index + 1, keyNumber - index);keys[index] = key;keyNumber++;}/*** 向節點中插入孩子節點* @param child* @param index*/void insertChild(Node child, int index) {System.arraycopy(children, index, children, index + 1, keyNumber - index);children[index] = child;}
}

這里我采用了靜態數組,因此需要多添加一個keyNumber參數來獲取有效key的數量,如果使用ArrayList,可以通過size方法獲取,因此不需要添加這個屬性。

大體框架

public class BTree {private Node root;private int t;//最小度數final int MAX_KEY_NUMBER;//最大key數量final int MIN_KEY_NUMBER;//最小key數量。用于分裂使用public BTree(int t) {this.t = t;root = new Node(t);MAX_KEY_NUMBER = 2 * t - 1;MIN_KEY_NUMBER = 2 * t;}static class Node {boolean leaf = true; //是否是葉子節點int keyNumber; //有效keyint t; //最小度int[] keys; // keys數組Node[] children; //孩子節點數組public Node(int t) {this.t = t;this.keys = new int[2 * t - 1];this.children = new Node[2 * t];//最大孩子節點個數為為2*t}@Overridepublic String toString() {return Arrays.toString(Arrays.copyOfRange(keys, 0, keyNumber));}/*** 根據key獲取對應節點** @param key* @return*/Node get(int key) {int i = 0;while (i < keyNumber) {//如果在該節點找到,那么直接返回即可if (keys[i] == key) {return this;}//說明要找的元素可能在children[i]中if (keys[i] > key) {break;}i++;}//如果是葉子節點,直接返回nullif (leaf) {return null;}return children[i].get(key);}/*** 指定位置插入元素** @param key* @param index*/void insertKey(int key, int index) {System.arraycopy(keys, index, keys, index + 1, keyNumber - index);keys[index] = key;keyNumber++;}/*** 向節點中插入孩子節點** @param child* @param index*/void insertChild(Node child, int index) {System.arraycopy(children, index, children, index + 1, keyNumber - index);children[index] = child;}}
}

實現分裂

先說分裂規律,當新增一個節點后使節點中的key滿足2t-1,那么該節點就會被分裂。

  • 創建一個新的節點,暫時稱為right節點(分裂后會在被分裂節點的右邊)
  • 被分裂節點把 t 以后的 key 和 child 都拷貝到right節點
  • t-1 處的 key 插入到 parent 的 index 處,index 指被分裂節點作為孩子時的索引
  • right 節點作為 parent 的孩子插入到 index + 1 處

圖示如下:

紅色部分的意思是當前節點是父結點中作為孩子的下標。黑色部分是key,小數字代表key的下標。

起始存在一個t為3的B樹。那么最大key就為 2*3-1 。此時作為父結點孩子下標為1的節點以及存在 4 個key,再添加一個key就會觸發分裂。

現在,再添加一個新的key值 8 ,此時到達最大key數,觸發分裂

此時分裂結束,分裂后結果如下

具體實現代碼如下

private void split(Node parent, int index, Node split) {if (parent == null) {//說明分割根節點,除了需要創建right節點之外,還需要創建parent節點parent = new Node(t);parent.leaf = false;parent.insertChild(split, 0);root = parent;}Node right = new Node(t);//將被分裂節點的一部分key放入right節點中。System.arraycopy(split.keys, index, right.keys, 0, t - 1);//新建的節點與被分裂節點在同一層,因此leaf屬性應該和被分裂節點一樣right.leaf = split.leaf;right.keyNumber = t - 1;//如果被分裂節點不是葉子節點,也需要將孩子節點一并拷貝到right節點中if (!split.leaf) {System.arraycopy(split.children, t, right.children, 0, t);}split.keyNumber = t - 1;//將t-1節點放入父結點中int mid = split.keys[t - 1];parent.insertKey(mid, index);parent.insertChild(right, index + 1);
}

實現新增

  • 首先查找本節點中的插入位置 i,如果沒有空位(key 被找到),應該走更新的邏輯。
  • 接下來分兩種情況
    • 如果節點是葉子節點,可以直接插入了
    • 如果節點是非葉子節點,需要繼續在 children[i] 處繼續遞歸插入
  • 無論哪種情況,插入完成后都可能超過節點 keys 數目限制,此時應當執行節點分裂
    • 參數中的 parent 和 index 都是給分裂方法用的,代表當前節點父節點,和分裂節點是第幾個孩子

具體實現代碼如下

public void put(int key) {doPut(null, 0, root, key);
}private void doPut(Node parent, int index, Node node, int key) {int i = 0;while (i < node.keyNumber && node.keys[i] < key) {i++;}// TODO i<node.keyNumber是否多余?if (i < node.keyNumber && node.keys[i] == key) {return;}if (node.leaf) {node.insertKey(key, i);} else {doPut(node, i, node.children[i], key);}if (isFull(node)) {split(parent, index, node);}
}private boolean isFull(Node node) {return node.keyNumber == MAX_KEY_NUMBER;
}

實現刪除

刪除節點會存在下面幾種情況

case 1:當前節點是葉子節點,沒找到。直接返回null

case 2:當前節點是葉子節點,找到了。直接移除節點即可

case 3:當前節點是非葉子節點,沒找到。遞歸尋找孩子節點是否存在該key

case 4:當前節點是非葉子節點,找到了。找到后驅節點交換key,并將交換后的key刪除

case 5:刪除后 key 數目 < 下限(不平衡)。需要進行調整

在兄弟節點中keyNumber數量充足的情況下可以通過旋轉調整平衡。圖示如下

現在要刪除節點 2

刪除之后,左側孩子的key數量少于最小限度,因此需要進行一次左旋。

父結點 3 移動到左側孩子節點中,右側孩子節點中的第一個key 5 移動到父結點中,左旋結束。

但如果兄弟節點的key數量是最小限度,那么此時應該進行合并,而不是旋轉。

合并時,我們通常選擇將右側的節點合并到左側節點中去。圖示如下

此時要刪除key 3 ,右側兄弟節點無法再向被刪除節點提供key。

于是將右側節點移除,同時將父結點的值與被移除節點的值都放在最初的左孩子節點中。

case 6:根節點

當經過合并之后,根結點可能會存在為null的情況,此時讓根節點中的 0 號孩子替代掉根節點就好。

具體實現代碼如下

/*** 移除指定元素** @param key*/
public void remove(int key) {doRemove(null,root,0, key);
}private void doRemove(Node parent,Node node,int index, int key) {//首先要獲取指定元素int i = 0;while (i < node.keyNumber && node.keys[i] < key) {i++;}if (node.leaf) {if (node.keys[i] == key) {//case 2:如果找到了并且是葉子節點node.removeKey(i);} else {//case 1:如果沒找到并且是葉子節點return;}} else {if (node.keys[i] == key) {//case 4:如果找到了但不是葉子節點//找到后驅節點并交換位置Node child = node.children[i + 1];while (!child.leaf) {child = child.children[0];}int nextKey = child.keys[0];node.keys[i] = nextKey;//之所以不直接調用孩子節點的removeKey方法是為了避免刪除后發生不平衡//child.removeKey(0);doRemove(node,child,i+1, nextKey);} else {//case 3:如果沒找到但不是葉子節點doRemove(node,node.children[i],i, key);}}//如果刪除后,節點中的key少于下限,那么需要進行調整if (node.keyNumber < MIN_KEY_NUMBER) {//平衡調整balance(parent,node,index);}
}/*** 調整B樹** @param parent 父結點* @param node   被調整節點* @param index  被調整節點在父結點中的孩子數組下標*/
private void balance(Node parent, Node node, int index) {//case 6 根節點if (node == root) {if (node.keyNumber==0 && node.children[0]!=null){root = node.children[0];}return;}Node leftChild = parent.leftSibling(index);Node rightChild = parent.rightSibling(index);//如果左邊孩子節點中的key值充足if (leftChild != null && leftChild.keyNumber > MIN_KEY_NUMBER) {//將父結點中的key賦值給nodenode.insertKey(parent.keys[index - 1], 0);if (!leftChild.leaf) {//如果左側孩子不是一個葉子節點,在旋轉過后,會導致keysNumber+1!=children。//因此將多出來的孩子賦值更多出來一個key的被調整節點node.insertChild(leftChild.removeRightmostChild(), 0);}//將左孩子中最右側元素賦值給父結點parent.keys[index - 1] = leftChild.removeRightmostKey();return;}//如果右邊充足if (rightChild != null && rightChild.keyNumber > MIN_KEY_NUMBER) {node.insertKey(parent.keys[index], node.keyNumber);if (!rightChild.leaf) {node.insertChild(rightChild.removeLeftmostChild(), node.keyNumber);}parent.keys[index] = rightChild.removeLeftmostKey();return;}//合并//如果刪除節點存在左兄弟,向左合并if (leftChild != null) {//將被刪除節點從父結點上移除parent.removeChild(index);//將父結點的被移除節點的前驅節點移動到左兄弟上leftChild.insertKey(parent.removeKey(index - 1), leftChild.keyNumber);node.moveToTarget(leftChild);} else {//如果沒有左兄弟,那么移除右兄弟節點,并將右兄弟移動到被刪除節點上。parent.removeChild(index+1);node.insertKey(parent.removeKey(index),node.keyNumber);rightChild.moveToTarget(node);}
}

完整代碼

public class BTree {private Node root;private int t;//最小度數private final int MAX_KEY_NUMBER;private final int MIN_KEY_NUMBER;public BTree(int t) {this.t = t;root = new Node(t);MAX_KEY_NUMBER = 2 * t - 1;MIN_KEY_NUMBER = t-1;}static class Node {boolean leaf = true; //是否是葉子節點int keyNumber; //有效keyint t; //最小度int[] keys; // keys數組Node[] children; //孩子節點數組public Node(int t) {this.t = t;this.keys = new int[2 * t - 1];this.children = new Node[2 * t];//最大孩子節點個數為為2*t}@Overridepublic String toString() {return Arrays.toString(Arrays.copyOfRange(keys, 0, keyNumber));}/*** 根據key獲取對應節點** @param key* @return*/Node get(int key) {int i = 0;while (i < keyNumber) {//如果在該節點找到,那么直接返回即可if (keys[i] == key) {return this;}//說明要找的元素可能在children[i]中if (keys[i] > key) {break;}i++;}//如果是葉子節點,直接返回nullif (leaf) {return null;}return children[i].get(key);}/*** 指定位置插入元素** @param key* @param index*/void insertKey(int key, int index) {System.arraycopy(keys, index, keys, index + 1, keyNumber - index);keys[index] = key;keyNumber++;}/*** 向節點中插入孩子節點** @param child* @param index*/void insertChild(Node child, int index) {System.arraycopy(children, index, children, index + 1, keyNumber - index);children[index] = child;}//移除指定元素int removeKey(int index) {int t = keys[index];System.arraycopy(keys, index + 1, keys, index, --keyNumber - index);return t;}//移除最左邊的元素int removeLeftmostKey() {return removeKey(0);}//移除最右邊的元素int removeRightmostKey() {return removeKey(keyNumber - 1);}//移除指定位置的孩子節點Node removeChild(int index) {//獲取被移除的節點Node t = children[index];//將被移除節點的后面元素向前移動一位System.arraycopy(children, index + 1, children, index, keyNumber - index);//將之前最后一位的引用釋放children[keyNumber] = null;//返回被引用元素return t;}//移除最左邊的孩子節點Node removeLeftmostChild() {return removeChild(0);}//移除最右邊的孩子節點Node removeRightmostChild() {return removeChild(keyNumber);}//移動指定節點到目標節點void moveToTarget(Node target) {int start = target.keyNumber;if (!leaf) {for (int i = 0; i <= keyNumber; i++) {target.children[start + i] = children[i];}}for (int i = 0; i < keyNumber; i++) {target.keys[target.keyNumber++] = keys[i];}}//獲取指定孩子節點的左邊節點Node leftSibling(int index) {return index > 0 ? children[index - 1] : null;}//獲取指定孩子節點的右邊節點Node rightSibling(int index) {return index == keyNumber ? null : children[index + 1];}}/*** 查詢key值是否在樹中** @param key* @return*/public boolean contains(int key) {return root.get(key) != null;}/*** 新增節點** @param key*/public void put(int key) {doPut(null, 0, root, key);}//index的作用是,給分割方法提供參數private void doPut(Node parent, int index, Node node, int key) {//尋找插入位置int i = 0;while (i < node.keyNumber && node.keys[i] < key) {i++;}//如果找到了,直接更新if (node.keys[i] == key) {return;}//如果沒找到,查看是否是葉子節點,如果不是,向下尋找if (node.leaf) {node.insertKey(key, i);} else {doPut(node, i, node.children[i], key);}if (isFull(node)) {split(parent, index, node);}}private boolean isFull(Node node) {return node.keyNumber == MAX_KEY_NUMBER;}/*** 分裂節點** @param parent* @param index 分割節點在父結點的孩子下標* @param split*/private void split(Node parent, int index, Node split) {if (parent == null) {//說明分割根節點,除了需要創建right節點之外,還需要創建parent節點parent = new Node(t);parent.leaf = false;parent.insertChild(split, 0);root = parent;}Node right = new Node(t);//將被分裂節點的一部分key放入right節點中。System.arraycopy(split.keys, t, right.keys, 0, t - 1);//新建的節點與被分裂節點在同一層,因此leaf屬性應該和被分裂節點一樣right.leaf = split.leaf;right.keyNumber = t - 1;//如果被分裂節點不是葉子節點,也需要將孩子節點一并拷貝到right節點中if (!split.leaf) {System.arraycopy(split.children, t, right.children, 0, t);for (int i =t;i<=split.keyNumber;i++){split.children[i]=null;}}split.keyNumber = t - 1;//將t-1節點放入父結點中int mid = split.keys[t - 1];parent.insertKey(mid, index);parent.insertChild(right, index + 1);}/*** 移除指定元素** @param key*/public void remove(int key) {doRemove(null,root,0, key);}private void doRemove(Node parent,Node node,int index, int key) {//首先要獲取指定元素int i = 0;while (i < node.keyNumber && node.keys[i] < key) {i++;}if (node.leaf) {if (node.keys[i] == key) {//case 2:如果找到了并且是葉子節點node.removeKey(i);} else {//case 1:如果沒找到并且是葉子節點return;}} else {if (node.keys[i] == key) {//case 4:如果找到了但不是葉子節點//找到后驅節點并交換位置Node child = node.children[i + 1];while (!child.leaf) {child = child.children[0];}int nextKey = child.keys[0];node.keys[i] = nextKey;//之所以不直接調用孩子節點的removeKey方法是為了避免刪除后發生不平衡//child.removeKey(0);doRemove(node,child,i+1, nextKey);} else {//case 3:如果沒找到但不是葉子節點doRemove(node,node.children[i],i, key);}}//如果刪除后,節點中的key少于下限,那么需要進行調整if (node.keyNumber < MIN_KEY_NUMBER) {//平衡調整balance(parent,node,index);}}/*** 調整B樹** @param parent 父結點* @param node   被調整節點* @param index  被調整節點在父結點中的孩子數組下標*/private void balance(Node parent, Node node, int index) {//case 6 根節點if (node == root) {if (node.keyNumber==0 && node.children[0]!=null){root = node.children[0];}return;}Node leftChild = parent.leftSibling(index);Node rightChild = parent.rightSibling(index);//如果左邊孩子節點中的key值充足if (leftChild != null && leftChild.keyNumber > MIN_KEY_NUMBER) {//將父結點中的key賦值給nodenode.insertKey(parent.keys[index - 1], 0);if (!leftChild.leaf) {//如果左側孩子不是一個葉子節點,在旋轉過后,會導致keysNumber+1!=children。//因此將多出來的孩子賦值更多出來一個key的被調整節點node.insertChild(leftChild.removeRightmostChild(), 0);}//將左孩子中最右側元素賦值給父結點parent.keys[index - 1] = leftChild.removeRightmostKey();return;}//如果右邊充足if (rightChild != null && rightChild.keyNumber > MIN_KEY_NUMBER) {node.insertKey(parent.keys[index], node.keyNumber);if (!rightChild.leaf) {node.insertChild(rightChild.removeLeftmostChild(), node.keyNumber);}parent.keys[index] = rightChild.removeLeftmostKey();return;}//合并//如果刪除節點存在左兄弟,向左合并if (leftChild != null) {//將被刪除節點從父結點上移除parent.removeChild(index);//將父結點的被移除節點的前驅節點移動到左兄弟上leftChild.insertKey(parent.removeKey(index - 1), leftChild.keyNumber);node.moveToTarget(leftChild);} else {//如果沒有左兄弟,那么移除右兄弟節點,并將右兄弟移動到被刪除節點上。parent.removeChild(index+1);node.insertKey(parent.removeKey(index),node.keyNumber);rightChild.moveToTarget(node);}}}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/164954.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/164954.shtml
英文地址,請注明出處:http://en.pswp.cn/news/164954.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

python每日一題——2字母異位詞分組

題目 給你一個字符串數組&#xff0c;請你將 字母異位詞 組合在一起。可以按任意順序返回結果列表。 字母異位詞 是由重新排列源單詞的所有字母得到的一個新單詞。 示例 1: 輸入: strs [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”] 輸出: [[“bat”],[“nat”…

新的centos7.9安裝jenkins—(一)

更多ruoyi-nbcio功能請看演示系統 gitee源代碼地址 前后端代碼&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后臺管理系統 因為是用java8&#xff0c;所以還是要最后java8版本的jenkins&#xff0c;版本號是2.346.3&#xff0c;后…

【Python】批量將PDG合成PDF,以及根據SS號重命名秒傳的文件

目錄 說明批量zip2pdf批量zip2pdf下載SS號重命名源代碼SS號重命名源代碼下載附錄&#xff0c;水文年鑒 說明 1、zip2pdf是一個開源軟件&#xff0c;支持自動化解壓壓縮包成PDG&#xff0c;PDG合成PDF&#xff0c;筆者在其基礎上做了部分修改&#xff0c;支持批量轉換。 2、秒…

【追求卓越11】算法--二叉樹

引導 接下來的幾節我們開始介紹非線性的數據結構--樹。樹的內容比較多也比較復雜。本節&#xff0c;我們只需要了解關于樹的一些基本概念。以及再進一步了解樹的相關內容--搜索二叉樹。該類型二叉樹在工作中&#xff0c;是我們常接觸的。該節我們介紹關于搜索二叉樹的相關操作&…

【華為數通HCIP | 網絡工程師】821-IGP高頻題、易錯題之OSPF(2)

個人名片&#xff1a; &#x1f43c;作者簡介&#xff1a;一名大三在校生&#xff0c;喜歡AI編程&#x1f38b; &#x1f43b;???個人主頁&#x1f947;&#xff1a;落798. &#x1f43c;個人WeChat&#xff1a;hmmwx53 &#x1f54a;?系列專欄&#xff1a;&#x1f5bc;?…

計算機中msvcr120.dll丟失怎樣修復?親測有效的5種方法分享

在計算機使用過程中&#xff0c;我們經常會遇到一些錯誤提示&#xff0c;其中之一就是“msvcr120.dll丟失”。這個錯誤通常會導致某些應用程序無法正常運行。那么&#xff0c;當我們遇到這個問題時&#xff0c;應該如何修復呢&#xff1f;本文將詳細介紹msvcr120.dll丟失的解決…

人工智能今天能為你做什么?生成式人工智能如何改變技術文檔領域

▲ 搜索“大龍談智能內容”關注GongZongHao▲ 作者 | Fabrice Lacroix 大型語言模型&#xff08;LLM&#xff09;和生成式人工智能&#xff08;GenAI&#xff09;&#xff0c;尤其是ChatGPT&#xff0c;這些是引領科技革新的新興技術。它們不僅在科技界引起了軒然大波&#x…

Web 自動化神器 TestCafe(三)—用例編寫篇

一、用例編寫基本規范 1、 fixture 測試夾具 使用 TestCafe 編寫測試用例&#xff0c;必須要先使用 fixture 聲明一個測試夾具&#xff0c;然后在這個測試夾具下編寫測試用例&#xff0c;在一個編寫測試用例的 js 或 ts 文件中&#xff0c;可以聲明多個測試夾具 fixture(測試…

【C++11】default、delete與Noncopyable

C11 oop中的default、delete與Noncopyable default 在C11標準中&#xff0c;可以使用default關鍵字來顯式地聲明默認的構造函數和析構函數。 使用default關鍵字可以用來顯式聲明默認的構造函數和析構函數。這樣做可以讓編譯器自動生成默認實現 –>->->關于構造函數…

計數排序+桶排序+基數排序 詳講(思路+圖解+代碼詳解)

文章目錄 計數排序桶排序基數排序一、計數排序概念&#xff1a;寫法一&#xff1a;寫法二&#xff1a; 二、桶排序概念代碼 三、基數排序概念1.LSD排序法&#xff08;最低位優先法&#xff09;2.MSD排序法&#xff08;最高位優先法&#xff09; 基數排序VS基數排序VS桶排序 計數…

內容營銷頻頻出圈,這些品牌號做對了什么?

小紅書擁有大量的年輕用戶&#xff0c;通過運營品牌號既能降低投放成本&#xff0c;又能更好地連接消費者和品牌&#xff0c;在平臺完成一站式閉環營銷。 今天就借助幾個成功案例&#xff0c;來分析下他們是如何搭建官方賬號&#xff0c;通過內容運營吸引更多用戶&#xff0c;實…

一款專為POS機設計的芯片解決方案

一、基本概述 HCM8003設計用于磁條讀卡器系統。它會從F/2F恢復時鐘和數據信號磁產生的數據流頭HCM8003將用于數據速率從200到15000比特每秒。 二、典型電路 內部數據的采集和跟蹤這個范圍是自動的。可以應用于POS機終端設備、磁卡門禁系統、身份識別等場合。 三、引腳定義 四…

redis的主從復制,哨兵模式

1.主從復制 主從復制&#xff1a;主從復制是redis實現高可用的基礎&#xff0c;哨兵模式和集群都是在主從復制的基礎之上實現高可用 主從復制實現數據的多機備份&#xff0c;以及讀寫分離&#xff08;主服務器負責寫&#xff0c;從服務器只能讀&#xff09; 缺陷&#xff1a…

PyTorch基本操作和工作流程

1. PyTorch基礎 張量&#xff08;Tensors&#xff09;&#xff1a; 學習 PyTorch 中表示數據的基本單元。了解如何創建、操作和使用張量。 自動微分&#xff08;Autograd&#xff09;&#xff1a; 了解 PyTorch 的自動微分機制&#xff0c;這是訓練神經網絡的核心。 模型定義…

Android registerForActivityResults使用詳解以及實現原理

registerForActivityResult 使用用途是監聽Activity結果。 以下是使用樣例 //需要傳遞Request用于解析Intent和解析上個Activity返回的結果 val launchdata = registerForActivityResult<PickVisualMediaRequest, Uri?>(ActivityResultContracts.PickVisualMedia()) {…

中國人工智能系列白皮書 AIGC

https://www.caai.cn/index.php?s/home/article/detail/id/3188.html

【git】使用ssh

前言 git之前一直使用https&#xff0c;因為很方便隨時隨地都可以用。最近把代碼托管到GitHub&#xff0c;使用https就使用不了。后面聽同事說GitHub使用ssh是沒問題的&#xff0c;就想著嘗試一下。 git ssh配置 設置用戶名和郵箱 git config --global use.name username g…

OpenCV實現圖像噪聲、去噪基本方法

一、噪聲分類 1、高斯噪聲 指服從高斯分布&#xff08;正態分布&#xff09;的一類噪聲&#xff0c;其產生的主要原因是由于相機在拍攝時視場較暗且亮度不均勻造成的&#xff0c;同時相機長時間工作使得溫度過高也會引起高斯噪聲&#xff0c;另外電路元器件白身噪聲和互相影響…

acwing算法基礎之數學知識--求組合數基礎版

目錄 1 基礎知識2 模板3 工程化 1 基礎知識 &#xff08;一&#xff09; 組合數 C n k C_n^k Cnk?的計算公式&#xff0c; C n k n ? ( n ? 1 ) ? ( n ? k 1 ) 1 ? 2 ? k C_n^k\frac{n\cdot(n-1)\cdots(n-k1)}{1\cdot 2\cdots k} Cnk?1?2?kn?(n?1)?(n?k1)? …