演算法之樹(一,B-樹原理詳解)(Java版)-持續更新補充
因為是複習,從基礎開始一起復習。
如果衝著標題來的,可以直接跳到後半部分看B樹的內容(~ ̄▽ ̄)~
支援雲棲社群!同時俺也有自己的獨立部落格——白水東城,因為在社群部落格裡只能發發技術文章之類的,但在自己部落格我會寫一些最近隨筆和讀書筆記等等哈哈,也希望大家能支援一下 ( •̀ ω •́ )y
這裡是我獨立部落格裡這篇文章的連結:
演算法之樹(一,B-樹原理詳解)(Java版)-持續更新補充
一、二叉樹
二叉樹的基本操作
public class BinaryTreeNode {
private int data;
private BinaryTreeNode leftChild;
private BinaryTreeNode rightChild;
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
public BinaryTreeNode getLeftChild() {
return leftChild;
}
public void setLeftChild(BinaryTreeNode leftChild) {
this.leftChild = leftChild;
}
public BinaryTreeNode getRightChild() {
return rightChild;
}
public void setRightChild(BinaryTreeNode rightChild) {
this.rightChild = rightChild;
}
}
public class BinaryTree {
private BinaryTreeNode root;
public BinaryTree() {
}
public BinaryTree(BinaryTreeNode root) {
this.root = root;
}
public void setRoot(BinaryTreeNode root) {
this.root = root;
}
public BinaryTreeNode getRoot() {
return root;
}
public void clear(BinaryTreeNode node) {
if(node != null) {
clear(node.getLeftChild());
clear(node.getRightChild());
node = null;
}
}
public void clear() {
clear(root);
}
public boolean isEmpty() {
return root == null;
}
public int height() {
return height(root);
}
public int height(BinaryTreeNode node) {
if(node == null) {
return 0;
}else {
int l = height(node.getLeftChild());
int r = height(node.getRightChild());
return l < r ? r + 1 : l + 1;
}
}
public int size() {
return size(root);
}
public int size(BinaryTreeNode node) {
if(node == null) {
return 0;
}else {
return 1 + size(node.getLeftChild()) + size(node.getRightChild());
}
}
public BinaryTreeNode getParent(BinaryTreeNode node) {
return (root == null || root == node) ? null : getParent(root, node);
}
public BinaryTreeNode getParent(BinaryTreeNode subTree, BinaryTreeNode node) {
if(subTree == null) {
return null;
}
if(subTree.getLeftChild() == node || subTree.getRightChild() == node) {
return subTree;
}
BinaryTreeNode parent = null;
if((parent = getParent(subTree.getLeftChild(),node)) != null) {
return parent;
}else {
return getParent(subTree.getRightChild(), node);
}
}
public BinaryTreeNode getLeftTree(BinaryTreeNode node) {
return node.getLeftChild();
}
public BinaryTreeNode getRightTree(BinaryTreeNode node) {
return node.getRightChild();
}
public void insertLeft(BinaryTreeNode parent, BinaryTreeNode newNode) {
parent.setLeftChild(newNode);
}
public void insertRight(BinaryTreeNode parent, BinaryTreeNode newNode) {
parent.setRightChild(newNode);
}
public void visited(BinaryTreeNode node) {
}
public void preOrder(BinaryTreeNode node) {
if(node != null) {
visited(node);
preOrder(node.getLeftChild());
preOrder(node.getRightChild());
}
}
public void inOrder(BinaryTreeNode node) {
if(node != null) {
inOrder(node.getLeftChild());
visited(node);
inOrder(node.getRightChild());
}
}
public void postOrder(BinaryTreeNode node) {
if(node != null) {
postOrder(node.getLeftChild());
postOrder(node.getRightChild());
visited(node);
}
}
}
二、二叉查詢樹
簡單來說就是左子樹結點值都小於根結點,右子樹都大於根結點。
二叉查詢樹的操作
public class BinarySearchingTree {
private BinaryTreeNode root;
public BinarySearchingTree(BinaryTreeNode root) {
this.root = root;
}
public BinaryTreeNode search(int data) {
return search(root, data);
}
//二叉查詢樹的插入操作很簡單,找到和待插入結點同樣值的結點則不插入,
//否則在樹的末尾新建一個結點,不需要變動其他結點
public void insert(int data) {
if(root == null) {
root = new BinaryTreeNode();
root.setData(data);
}else {
searchAndInsert(null, root, data);
}
}
//如果待刪除的結點左右子樹都不為空,
//則選擇右子樹的最左結點或者左子樹的最右結點來代替
public void delete(int data) {
if(root.getData() == data) {
root = null;
return;
}
//知道要刪除那個結點的父結點很關鍵
//但無法確定要刪除的結點是其父結點的左還是右結點
//需要特別判斷一下
BinaryTreeNode parent = searchParent(data);
if(parent == null) {
return;
}
BinaryTreeNode node = search(parent, data);
if(node.getLeftChild() == null && node.getRightChild() == null) {
//葉子結點,直接刪除
if(parent.getLeftChild() != null && parent.getLeftChild().getData() == data) {
parent.setLeftChild(null);
}else {
parent.setRightChild(null);
}
}else if(node.getLeftChild() != null && node.getRightChild() == null) {
//左子樹不為空
if(parent.getLeftChild()!= null && parent.getLeftChild().getData() ==data) {
parent.setLeftChild(node.getLeftChild());
}else {
parent.setRightChild(node.getRightChild());
}
}else if(node.getLeftChild() == null && node.getClass() != null) {
//右子樹不為空
if(parent.getLeftChild() != null && parent.getLeftChild().getData() == data) {
parent.setLeftChild(node.getRightChild());
}else {
parent.setRightChild(node.getRightChild());
}
}else {
//左右子樹都不為空
//查詢右子樹最左子節點
BinaryTreeNode deleteNode = node;
BinaryTreeNode temp = node.getRightChild();
//1. 右子樹沒有左孩子,直接上移
if(temp.getLeftChild() == null) {
temp.setLeftChild(deleteNode.getLeftChild());
}else {
while(temp.getLeftChild() != null) {
node = temp;
temp = temp.getLeftChild();
}
//2. 繼承節點原右子樹上移
node.setLeftChild(temp.getRightChild());
//3. 繼承節點就位
temp.setLeftChild(deleteNode.getLeftChild());
temp.setRightChild(deleteNode.getRightChild());
}
//4. 更新父節點為刪除結點的原父節點
if(parent.getLeftChild() != null && parent.getLeftChild().getData() == data) {
parent.setLeftChild(temp);
}else {
parent.setRightChild(temp);
}
}
}
public BinaryTreeNode searchParent(int data) {
return searchParent(null, root, data);
}
public BinaryTreeNode getRoot() {
return root;
}
private BinaryTreeNode searchAndInsert(BinaryTreeNode parent, BinaryTreeNode node, int data) {
if(node == null) {
node = new BinaryTreeNode();
node.setData(data);
if(data > parent.getData()) {
parent.setRightChild(node);
}else {
parent.setLeftChild(node);
}
return node;
}else if(node.getData() == data) {
return node;
}else if(data > node.getData()) {
return searchAndInsert(node, node.getRightChild(), data);
}else {
return searchAndInsert(node, node.getLeftChild(), data);
}
}
private BinaryTreeNode search(BinaryTreeNode node, int data) {
if(node == null) {
return null;
}else if(node.getData() == data) {
return node;
}else if(data > node.getData()) {
return search(node.getRightChild(), data);
}else {
return search(node.getLeftChild(), data);
}
}
private BinaryTreeNode searchParent(BinaryTreeNode parent, BinaryTreeNode node, int data) {
if(node == null) {
return null;
}else if(node.getData() == data) {
return parent;
}else if(data > node.getData()) {
return searchParent(node, node.getRightChild(), data);
}else {
return searchParent(node, node.getLeftChild(), data);
}
}
}
三、平衡二叉樹
Balanced Binary Tree,又叫AVL樹(由提出者名字縮寫而來)。簡單來說,在二叉查詢樹的基礎上,要保持左右子樹高度差不超過1,我們把左子樹高度減去右子樹高度叫做平衡因子(Balanced Factor,BF)
二叉查詢樹查詢的時間複雜度最好是O(logn),最壞是O(n),而AVL最好最壞都是O(logn),插入和刪除也是O(logn)。
程式碼暫時省略,以後回來補充
四、B-樹、B+樹
B-樹
B減樹和B樹是一個意思,那個不是減號而是短橫。這個不糾結,意思明白就行。
B樹是用於在外存工作的平衡搜尋樹,MySQL中的索引主要是基於hash表或者B+樹。
為什麼不用二叉查詢樹?
資料庫索引為啥不用二叉查詢樹實現呢?
二叉查詢樹的時間複雜度為O(logn),從演算法邏輯上講查詢速度和比較次數都是最小的。但是,現實要考慮 磁碟IO。
資料庫索引是存在磁碟上的,因為資料量很大的時候索引大小可能達到幾個G。所以在利用索引查詢的時候,不能把整個索引全部載入到記憶體,只有逐一載入每一個磁碟頁,這裡磁碟頁對應著索引樹的節點,如圖(圖片源於程式設計師小灰)。
當資料比較大,無法全部存入記憶體時,需要將部分資料存在外存中,在需要的時候讀入記憶體,修改之後又寫回外存。由於外存的速度與記憶體有幾個數量級的差別,所以節省在外存上花的時間,對搜尋樹的效能提高時最有效的。
最常見的外存就是磁碟。磁碟是塊裝置,也就是說磁碟的讀寫單位是以塊為單位,一般地塊大小從0.5k到4k。即使你只讀取一個位元組,磁碟也是將包含該位元組的所有資料讀取到硬碟中。而在磁碟讀取過程中,最佔用時間的是磁碟的尋道,也就是磁頭在碟片上找到需要讀取的塊所在位置的時間,而在碟片上順序讀取資料的所花的時間是佔比比較小的。
要減少外存上花的時間,就可以從減少讀盤次數以及減少尋道時間著手。B樹採取的方法就是,就充分的利用盤塊的空間,在一個盤塊中儘可能多的儲存資訊,或者在連續的盤塊地址上儲存儘可能多的資訊。在資料結構上的變化就是每個節點儲存多個key資訊以及包含多個子節點。
磁碟的IO次數由樹的高度決定的,在最壞的情況下磁碟的IO數等於索引樹的高度。而B-樹是一棵平衡的m-路查詢樹,一個m-路查詢樹,高度為h,每一個節點最多容納m-1個關鍵字,所以一棵m-路查詢樹總共可容納m^k - 1
個關鍵字。
與二叉查詢樹比較,當高度為h,能容納2^h - 1
個關鍵字,高度若為3,則二叉查詢樹只能容納7個關鍵字;而對於200-路查詢樹可以容納200^3 - 1
個關鍵字!
再說回來,為了減少磁碟的IO次數,就需要把原來“瘦高”的樹結構變得“矮胖”,而這個正滿足B樹的特徵之一。
B樹是多路平衡查詢樹,它的每一個節點最多包含k個孩子,k被稱為B樹的階,k的大小取決於磁碟頁的大小。就像剛才舉例200-路查詢樹一樣。
B樹檔案查詢的具體過程
假設每一個磁碟頁正好存放一個B樹的節點,而子樹的指標就是存放另一個磁碟頁的地址。
那麼查詢操作就是:首先是根節點(從磁碟調出資料,進行第一次磁碟I/O,資料讀入記憶體進行查詢),記憶體中可以順序也可以二分,如果找到要查詢的那個數則OK,否則要確認指標的位置,也就是確認是那棵子樹,然後遞迴下去。
圖片來自: v_JULY_v的CSDN部落格
B樹的特徵
- 根結點至少有兩個子女。
- 每個中間節點都包含k-1個元素和k個孩子,其中 m/2 <= k <= m
- 每一個葉子節點都包含k-1個元素,其中 m/2 <= k <= m
- 所有的葉子結點都位於同一層。
- 每個節點中的元素從小到大排列,節點當中k-1個元素正好是k個孩子包含的元素的值域分劃。
B樹在查詢中比較的次數不比二叉查詢樹少,但是因為B樹把多個關鍵字放在了同一個節點中,這樣減少了磁碟的IO次數,同時在記憶體中比較耗時幾乎可以忽略,所以只要樹高度足夠低,IO次數足夠少,就能調高查詢效能。
B樹的應用場景
B-樹主要用於檔案系統以及部分資料庫索引,比如非關係型資料庫MongoDB。
B樹的程式碼實現
先mark在這,暫時不打算搞。
OK,下一篇接著搞樹!總結了幾個大牛的書和部落格的內容,記下筆記,也希望能對你有幫助( ̄︶ ̄)↗
看到這裡一定是真愛了,有啥疑惑可以留言噢~~
沒有的話,再看看我的獨立部落格——白水東城也OK!哈哈
獨立部落格剛搞不久,支援雲棲社群,也希望大家能支援下俺的部落格= ̄ω ̄=
參考
- 《輕鬆學演算法》趙燁
- 漫畫:什麼是B-樹(程式設計師小灰)
- 從B樹、B+樹、B*樹談到R 樹
- B樹(Java)實現
相關文章
- 演算法之排序(Java版)-持續更新補充演算法排序Java
- 演算法之搜尋(Java版)-持續更新補充演算法Java
- lambda(持續補充)
- 詳解什麼是平衡二叉樹(AVL)(修訂補充版)二叉樹
- 技術資源下載(持續補充更新)
- webpack使用優化(持續更新,歡迎補充)Web優化
- 快速生成樹原理詳解
- 上下文詳解(持續更新)
- 『分享』兩篇講 B-樹 B+ 樹的文章
- JAVA類庫之——Character類(持續更新)Java
- 1.1. 電阻篇----硬體設計指南(持續補充更新)
- 咬文嚼圖式的介紹二叉樹、B樹/B-樹二叉樹
- CTFHub技能樹web(持續更新)--檔案上傳--雙寫繞過Web
- 【PyTorch】常用的神經網路層彙總(持續補充更新)PyTorch神經網路
- 1.3 功率電感選型----硬體設計指南(持續補充更新)
- 【字串演算法】字典樹詳解字串演算法
- 資料結構之樹結構概述(含滿二叉樹、完全二叉樹、平衡二叉樹、二叉搜尋樹、紅黑樹、B-樹、B+樹、B*樹)資料結構二叉樹
- Prim演算法(最小生成樹) 我TM只能先做到這兒了 後面繼續補充演算法
- 查詢|有序表折半查詢判定樹|二叉排序樹|3階B-樹排序
- JAVA系列合集(持續更新中)Java
- LSM 樹詳解
- 貓樹詳解
- 二叉搜尋樹演算法詳解與Java實現演算法Java
- 演算法導論學習--紅黑樹詳解之刪除(含完整紅黑樹程式碼)演算法
- 線段樹詳解 (原理,實現與應用)
- zkw 線段樹-原理及其擴充套件套件
- Java 學習筆記(持續更新)Java筆記
- 紅黑樹詳解
- 決策樹演算法原理(上)演算法
- 決策樹演算法原理(下)演算法
- javascript演算法彙總(持續更新中)JavaScript演算法
- leetcode題解【持續更新】LeetCode
- mdk的命令講解(持續更新)
- JVM(持續更新。。。)JVM
- FastApi持續更新ASTAPI
- Java基礎異常整理(持續更新)Java
- 【資料結構】B樹、B+樹詳解資料結構
- android之常用cmd命令(持續更新......)Android