【演算法】B+樹的研讀及實現(2)---java版核心程式碼
阿新 • • 發佈:2018-12-25
【前言】
假如大家已經弄懂了b樹及b+樹那麼恭喜你們了,因為我覺得,b樹及b+樹是檔案系統尤其是資料庫優化的關鍵。
這裡預告一下,下一篇課題(也不能說課題,只能用“業餘研究題目”這種稱呼)是R樹,R樹似乎是多維的B+樹,各位假如也希望弄懂R樹的話,請先好好看看b+樹。
【程式碼】
package BPlusTree; import java.util.ArrayList; public class INode { public boolean isLeaf=false; public INode parent=null; public ArrayList<Float> keys=new ArrayList<Float>(); public ArrayList<INode> childNodes=new ArrayList<INode>(); }
package BPlusTree;
public class TreeNode extends INode {
}
package BPlusTree;
import java.util.ArrayList;
public class TreeLeaf extends INode {
public ArrayList<Object> values=new ArrayList<Object>();
public TreeLeaf rightBrother=null;
}
【上面的三個類分別是節點父類,普通節點子類,葉子節點子類】
【核心操作類】
package BPlusTree; import java.util.ArrayList; public class TreeGen { private int _m=4;//--最大子樹數目 private int _min=2;//--最小關鍵字 public int getM(){ return _m; } public int getMin(){ return _min; } private INode _rootNode=new TreeLeaf(); public TreeGen(int m){ _m=m; _min=(int)Math.ceil((double)((double)_m/(double)2)); } /** * * 複製當前的陣列,變成一個不同的值。 * 這個用來記錄上一步驟的結果,免得到時候找不回以前的方法了。 * * */ public static TreeGen getCopyGen(TreeGen gen ){ TreeGen _gen1=new TreeGen(gen.getM()); ArrayList<Float> arrList=gen.getAllKeyList(); for(float f1:arrList){ _gen1.insert(f1); } return _gen1; } public void setGen(int m,int min,INode inode){ _m=m; _min=min; _rootNode=inode; } public INode getRootNode(){ return _rootNode; } /** * b+樹的插入演算法,這裡解釋一下流程: * 1、當最初插入關鍵字的時候,即根節點就是葉子節點,那麼直接插入,假如插入後根節點的關鍵字數量大於m,那麼需要分裂; * 2、尋找需要插入到的葉子節點,假如已經包含該關鍵字,報錯,否則插入該關鍵字,檢視關鍵字數量是否達到分裂條件,假如達到,則進行分裂操作。 * 必須注意的是,我這裡的b+樹對於大小的判斷是:第一個子樹的關鍵字必然大於或等於父親的第一個關鍵字,小於父親第二個關鍵字,如此類推, * 網上的演算法也有第一個子樹的所有關鍵字都小於或等於父親的第一個關鍵字這種。 * 我採用的演算法意味著在處理(插入)一個小於所有關鍵字的關鍵字時候,需要尋找最左邊的葉子節點,插入,然後修改該路徑所有節點的第一個索引為該關鍵字, * 同時需要進行正常的檢查分裂操作 * */ public boolean insert(float indexNO){ //--假如是前幾次插入,根節點為空或者下面都沒有子樹,那麼就直接變成葉子節點。 if(_rootNode.childNodes.size()<=0){ if(_rootNode.isLeaf==false){ TreeLeaf myRoot=new TreeLeaf(); myRoot.isLeaf=true; for(float keyNO:_rootNode.keys){ myRoot.keys.add(keyNO); } _rootNode=myRoot; } //--插入關鍵字。 int indexLOC=-1; int cindex=0; for(float f1:_rootNode.keys){ if(f1==indexNO){ return false;//包含該關鍵字,無法插入。 } if(indexNO>f1){ indexLOC=cindex; } if(indexNO<f1){ break; } cindex++; } _rootNode.keys.add(indexLOC+1, indexNO); recurse_division_after_insert(_rootNode); return true; } else{ TreeLeaf theLeaf=recursion_search_suitable_leaf(_rootNode, indexNO); if(theLeaf==null){ return false; } //--插入關鍵字。 int indexLOC=-1; int cindex=0; for(float f1:theLeaf.keys){ if(f1==indexNO){ return false;//包含該關鍵字,無法插入。 } if(indexNO>f1){ indexLOC=cindex; } if(indexNO<f1){ break; } cindex++; } insertIndexNO(theLeaf, indexNO); if(indexLOC==-1){ //--假如是-1,那麼表明它這一條路徑的所有父節點的第一個關鍵字都必須改為當前indexNO recursion_changeMinimun(theLeaf, indexNO); } recurse_division_after_insert(theLeaf); } return true; } public INode search(float indexNO){ _research_result=null; recursion_to_serach(_rootNode, indexNO); return _research_result; } private INode _research_result=null; private void recursion_to_serach(INode currentNode,float indexNO){ if(currentNode==null){ return; } if(currentNode.isLeaf==false&¤tNode.childNodes.size()>0){ int indexLoc=-1; int cindex=0; for(float key:currentNode.keys){ if(indexNO<key){ break; } if(indexNO>=key){ indexLoc=cindex; } cindex++; } /** * 假如是-1,表示所有的數都比它大,表明b+樹裡面沒有這個數,請回。 * */ if(indexLoc==-1){ return; } else{ recursion_to_serach(currentNode.childNodes.get(indexLoc), indexNO); return; } } else{ int indexLoc=-1; int cindex=0; for(float key:currentNode.keys){ if((float)indexNO==(float)key){ indexLoc=cindex; _research_result=currentNode; break; } cindex++; } } } private void recursion_changeMinimun(INode currentNode,float indexNO){ if(currentNode==null){ return; } if(currentNode.keys.get(0)!=indexNO){ currentNode.keys.remove(0); currentNode.keys.add(0, indexNO); } recursion_changeMinimun(currentNode.parent, indexNO); } private boolean insertIndexNO(INode currentNode,float indexNO){ if(currentNode==null){ return false; } int indexLOC=-1; int cindex=0; for(float f1:currentNode.keys){ if(f1==indexNO){ return false; } if(indexNO>f1){ indexLOC=cindex; } if(indexNO<f1){ break; } cindex++; } currentNode.keys.add(indexLOC+1, indexNO); return true; } private TreeLeaf recursion_search_suitable_leaf(INode currentNode,float indexNO){ if(currentNode==null){ return null; } if(currentNode.isLeaf==true||currentNode.childNodes.size()<=0){ return (TreeLeaf)currentNode; } int indexLoc=-1; int cindex=0; for(float iNO:currentNode.keys){ if(indexNO<iNO){ break; } if(indexNO>iNO){ indexLoc=cindex; } if(indexNO==iNO){ return null;//---節點裡麵包含該關鍵字,那麼只能說,已經插入同樣的關鍵字了,返回null } cindex++; } //--假如這個關鍵字是最少的,那麼就直接找到最左邊的葉子節點。 if(indexLoc==-1){ return recursion_getLeftLeaf(currentNode); } else{ return recursion_search_suitable_leaf(currentNode.childNodes.get(indexLoc), indexNO); } } private TreeLeaf recursion_getLeftLeaf(INode currentNode){ if(currentNode==null){ return null; } if(currentNode.isLeaf==true){ return (TreeLeaf)currentNode; } if(currentNode.childNodes.size()<=0){ return null; } return recursion_getLeftLeaf(currentNode.childNodes.get(0)); } /** * 在插入關鍵字後,需要對當前節點及其父節點進行樹形調整。 * */ private void recurse_division_after_insert(INode currentNode){ //--當前節點符合條件,那麼不用分裂 if(currentNode.keys.size()<=_m){ return; } TreeLeaf currentLeaf=null; TreeNode currentNode2=null; INode parentNode=currentNode.parent; if(currentNode.isLeaf==true){ currentLeaf=(TreeLeaf)currentNode; TreeLeaf rightLeaf=new TreeLeaf(); rightLeaf.parent=currentLeaf.parent; rightLeaf.isLeaf=true; rightLeaf.rightBrother=currentLeaf.rightBrother; currentLeaf.rightBrother=rightLeaf; int cindex=0; for(float f1:currentLeaf.keys){ if(cindex>=_min){ rightLeaf.keys.add(f1); if(currentLeaf.values.size()>cindex){ rightLeaf.values.add(currentLeaf.values.get(cindex)); } } cindex++; } int theOriginTotal=currentLeaf.keys.size(); for(int i1=theOriginTotal-1;i1>=_min;i1--){ currentLeaf.keys.remove(i1); if(currentLeaf.values.size()>i1){ currentLeaf.values.remove(i1); } } //---假如父節點為空,那麼直接開一個父節點。 if(currentLeaf.parent==null){ TreeNode theRoot=new TreeNode(); theRoot.keys.add( currentLeaf.keys.get(0)); theRoot.keys.add(rightLeaf.keys.get(0)); theRoot.childNodes.add(currentLeaf); theRoot.childNodes.add(rightLeaf); currentLeaf.parent=theRoot; rightLeaf.parent=theRoot; _rootNode=theRoot; return; } else{ int cLindex=parentNode.childNodes.indexOf(currentNode); parentNode.keys.add(cLindex+1, rightLeaf.keys.get(0)); parentNode.childNodes.add(cLindex+1, rightLeaf); recurse_division_after_insert(parentNode); return; } } else{ currentNode2=(TreeNode)currentNode; TreeNode normalNode=currentNode2; TreeNode rightBrother=new TreeNode(); rightBrother.parent=parentNode; int originTotal=normalNode.keys.size(); for(int i1=_min;i1<=originTotal-1;i1++){ rightBrother.keys.add(normalNode.keys.get(i1)); normalNode.childNodes.get(i1).parent=rightBrother; rightBrother.childNodes.add(normalNode.childNodes.get(i1)); } for(int i1=originTotal-1;i1>=_min;i1--){ normalNode.childNodes.remove(i1); normalNode.keys.remove(i1); } if(parentNode==null){ //--假如父節點為空,那麼只能另起節點分裂了。 TreeNode theRoot=new TreeNode(); theRoot.keys.add(normalNode.keys.get(0)); theRoot.keys.add(rightBrother.keys.get(0)); theRoot.childNodes.add(normalNode); theRoot.childNodes.add(rightBrother); normalNode.parent=theRoot; rightBrother.parent=theRoot; _rootNode=theRoot; return; } else{ int cLindex=parentNode.childNodes.indexOf(normalNode); parentNode.keys.add(cLindex+1, rightBrother.keys.get(0)); parentNode.childNodes.add(cLindex+1, rightBrother); recurse_division_after_insert(parentNode); return; } } } /** * B+樹的刪除有幾種情形(假如這是演算法的話,這就是刪除演算法了)。 * 原則: * 1、所有的刪除操作都是在葉子節點進行的,每次刪除都請先定位相關葉子節點; * 2、必須保持b+樹的原則,具體舉例(以本文實現的B+樹演算法而言),除了根節點,所有節點的關鍵字數量必須大於或等於_min, * 小於或等於_m,當小於_min的時候,可以這樣處理: * 2A、假如左右葉子節點有多餘的關鍵字,可以向左右借; * 2B、假如左右節點沒有,那麼只能合併節點,然後遞迴。(這種情況是進行遞迴的唯一情況)。 * * 其實這些操作與B樹的操作類似。 * */ public boolean delete(float indexNO){ INode currentNode=search(indexNO); if(currentNode==null){ return false; } /** *假如該節點是根節點,那麼刪了就刪了,不用怕。 * */ if(currentNode.parent==null&¤tNode.childNodes.size()<=0){ int indexLoc=getIndexLocation(currentNode, indexNO); if(indexLoc==-1){ return false; } else{ currentNode.keys.remove(indexLoc); return true; } } /** * 假如該節點是根節點,但是並非葉節點----不可能的情況,因為刪除必須從葉節點開始! * */ else if(currentNode.parent==null&¤tNode.childNodes.size()>0){ return false; } /** * 假如該節點不是根節點,但是也並非葉節點----請明白刪除必須從葉節點開始的含義! * */ else if(currentNode.parent!=null&¤tNode.childNodes.size()>0){ return false; } /** * 假如該節點是葉子節點。 * * */ else if(currentNode.childNodes.size()<=0){ /** * 假如葉子節點的關鍵字數量足夠,那麼直接刪除。 * */ if(currentNode.keys.size()>_min){ int indexLoc=getIndexLocation(currentNode, indexNO); /** * 假如刪除的是第一個關鍵字,請注意了,這種時候,它的上一級的第一位都必須更換成新的位置 * */ if(indexLoc==0){ currentNode.keys.remove(indexLoc); recursion_handler_firstOneDelete(null, currentNode, 0.0f); return true; } /** * 否則,直接刪除。 * */ else{ currentNode.keys.remove(indexLoc); return true; } } /** * 關鍵字數量不足,這時候: * 1、看看左右節點有沒有可以使用的,有的話,那麼就借用並調整樹形; * 2、沒有的話就刪除,合併然後遞迴調整樹形。 * */ else{ INode parentNode=currentNode.parent; int indexLoc=getIndexLocation(currentNode, indexNO); int cNodePindex=parentNode.childNodes.indexOf(currentNode); if(cNodePindex==-1){ return false; } INode leftBrother=null; INode rightBrother=((TreeLeaf)currentNode).rightBrother; if(cNodePindex>0){ leftBrother=parentNode.childNodes.get(cNodePindex-1); } /** * 有左側節點並且左側節點有足夠的關鍵字,借用之。 * */ if(leftBrother!=null&&leftBrother.keys.size()>_min){ currentNode.keys.remove(indexLoc); currentNode.keys.add(0, leftBrother.keys.get(leftBrother.keys.size()-1)); leftBrother.keys.remove(leftBrother.keys.size()-1); recursion_handler_firstOneDelete(null, currentNode, 0.0f); return true; } /** * 有右側節點並且右側節點有足夠的關鍵字,借用之。 * */ else if(rightBrother!=null&&rightBrother.keys.size()>_min){ currentNode.keys.remove(indexLoc); currentNode.keys.add(rightBrother.keys.get(0)); rightBrother.keys.remove(0); recursion_handler_firstOneDelete(null, rightBrother, 0.0f); if(indexLoc==0){ recursion_handler_firstOneDelete(null, currentNode, 0.0f); } return true; } /** * 最麻煩的情況也是需要遞迴的情況出現了,左右都沒有足夠的關鍵字,只能合併了,搞不好 * b+樹會層層合併,高度最後減-1。 * */ else{ /** * 跟左側兄弟合併 * */ if(leftBrother!=null){ currentNode.keys.remove(indexLoc); if(indexLoc==0){ recursion_handler_firstOneDelete(null, currentNode, 0.0f); } for(float f1:currentNode.keys){ leftBrother.keys.add(f1); } ((TreeLeaf)leftBrother).rightBrother=((TreeLeaf)currentNode).rightBrother; parentNode.keys.remove(cNodePindex); parentNode.childNodes.remove(cNodePindex); recursion_combination(parentNode); return true; } /** * 跟右側兄弟合併。 * */ else if(rightBrother!=null){ currentNode.keys.remove(indexLoc); if(indexLoc==0){ recursion_handler_firstOneDelete(null, currentNode, 0.0f); } for(float f1:rightBrother.keys){ currentNode.keys.add(f1); } ((TreeLeaf)currentNode).rightBrother=((TreeLeaf)rightBrother).rightBrother; parentNode.keys.remove(cNodePindex+1); parentNode.childNodes.remove(cNodePindex+1); recursion_combination(parentNode); return true; } else{ return false; } } } } /** * 其他情況不受理。 * */ else{ return false; } } private void recursion_handler_after_deletion(INode curretNode){} private void recursion_handler_firstOneDelete(INode childNode,INode currentNode,float firstIndexNO){ if(currentNode==null){ return; } INode parentNode=currentNode.parent; /** * 假如是葉節點,那麼就從這裡開始。 * */ if(currentNode.isLeaf==true){ if(parentNode!=null){ float myFirst=currentNode.keys.get(0); int pIndex=parentNode.childNodes.indexOf(currentNode); recursion_handler_firstOneDelete(currentNode,parentNode, myFirst); } return; } else{ int childIndexLoc=currentNode.childNodes.indexOf(childNode); if(childIndexLoc==-1){ return; } if((float)currentNode.keys.get(childIndexLoc)==firstIndexNO){} else{ if(childIndexLoc>0){ currentNode.keys.remove(childIndexLoc); currentNode.keys.add(childIndexLoc,firstIndexNO); if(parentNode!=null){ float cIndexNO=currentNode.keys.get(0); recursion_handler_firstOneDelete(currentNode,parentNode, cIndexNO); } return; } else if(childIndexLoc==0){ currentNode.keys.remove(0); currentNode.keys.add(0,firstIndexNO); float cIndexNO=currentNode.keys.get(0); recursion_handler_firstOneDelete(currentNode, parentNode, cIndexNO); return; } else{ return; } } } } private int getIndexLocation(INode currentNode,float indexNO){ int indexLoc=-1; if(currentNode==null){ return indexLoc; } int cindex=0; for(float f1:currentNode.keys){ if(f1==indexNO){ indexLoc=cindex; break; } cindex++; } return cindex; } /** * 當葉子節點需要合併,那麼必須遞迴進行處理合並。 * */ private void recursion_combination(INode currentNode){ if(currentNode==null){ return; } INode parentNode=currentNode.parent; if(currentNode.keys.size()>=_min){ return; } /** * 假如這個節點只有1,這種情況只會發生在剛好分成兩份的根節點被刪除一個然後需要合併,葉子節點合併後的情況,那麼 * 只能rootNode改變一下。 * */ if(currentNode.keys.size()==1&&parentNode==null){ _rootNode=currentNode.childNodes.get(0); _rootNode.parent=null; return; } /** * 否則,這個是根節點,有兩個關鍵字是可以的。 * */ if(parentNode==null&¤tNode.keys.size()>=2){ return; } INode leftBrother=null; INode rightBrother=null; int theCPindex=parentNode.childNodes.indexOf(currentNode); if(theCPindex==-1){ return; } if(theCPindex==0){ rightBrother=parentNode.childNodes.get(1); } else if(theCPindex==parentNode.childNodes.size()-1){ leftBrother=parentNode.childNodes.get(theCPindex-1); } else{ leftBrother=parentNode.childNodes.get(theCPindex-1); rightBrother=parentNode.childNodes.get(theCPindex+1); } /** * 假如左側有空餘的關鍵字,那麼就借用。 * */ if(leftBrother!=null&&leftBrother.keys.size()>_min){ currentNode.keys.add(0, leftBrother.keys.get(leftBrother.keys.size()-1)); currentNode.childNodes.add(0,leftBrother.childNodes.get(leftBrother.childNodes.size()-1)); currentNode.childNodes.get(0).parent=currentNode; leftBrother.keys.remove(leftBrother.keys.size()-1); leftBrother.childNodes.remove(leftBrother.childNodes.size()-1); parentNode.keys.remove(theCPindex); parentNode.keys.add(theCPindex,currentNode.keys.get(0)); return; } /** * 假如右側有空餘關鍵字。 * */ else if(rightBrother!=null&&rightBrother.keys.size()>_min){ currentNode.keys.add(rightBrother.keys.get(0)); currentNode.childNodes.add(rightBrother.childNodes.get(0)); currentNode.childNodes.get(currentNode.childNodes.size()-1).parent=currentNode; rightBrother.keys.remove(0); rightBrother.childNodes.remove(0); parentNode.keys.remove(theCPindex+1); parentNode.keys.add(theCPindex+1,rightBrother.keys.get(0)); return; } /** * 都沒有多餘的話,只能合併了,需要遞迴檢查。 * */ else{ /** * 假如左側兄弟有,那麼與左邊兄弟合併。 * */ if(leftBrother!=null){ for(Float key1:currentNode.keys){ leftBrother.keys.add(key1); } for(INode tmpNode:currentNode.childNodes){ tmpNode.parent=leftBrother; leftBrother.childNodes.add(tmpNode); } parentNode.keys.remove(theCPindex); parentNode.childNodes.remove(theCPindex); /** * 合併完畢,遞迴到上一級再合併。 * */ recursion_combination(parentNode); return; } /** * 假如有右邊兄弟,那麼與右邊合併。 * */ else if(rightBrother!=null){ for(Float key1:rightBrother.keys){ currentNode.keys.add(key1); } for(INode tmpNode:rightBrother.childNodes){ tmpNode.parent=currentNode; currentNode.childNodes.add(tmpNode); } parentNode.keys.remove(theCPindex+1); parentNode.childNodes.remove(theCPindex+1); /** * 合併完畢,遞迴。 * */ recursion_combination(parentNode); return; } else{ return; } } } private TreeLeaf _leaf_tmp=null; private void recursion_search_first_leaf(INode currNode){ if(currNode==null){ return; } if(currNode.isLeaf){ _leaf_tmp=(TreeLeaf)currNode; return; } else{ if(currNode.childNodes.size()<=0){ return; } else{ recursion_search_first_leaf(currNode.childNodes.get(0)); return; } } } public TreeLeaf getFirstLeaf(){ _leaf_tmp=null; recursion_search_first_leaf(_rootNode); return _leaf_tmp; } public ArrayList<Float> getAllKeyList(){ ArrayList<Float> flist=new ArrayList<Float>(); TreeLeaf fLeaf=getFirstLeaf(); while(fLeaf!=null){ for(float f2:fLeaf.keys){ flist.add(f2); } fLeaf=fLeaf.rightBrother; } return flist; } }
【後語】
ok,b+樹已經結束了,以後肯定要經常回看才能記得這種麻煩的資料結構,目前為止,已經編寫了幾種資料結構的演示程式了,分別是紅黑樹,b樹,b+樹,下一個題目應該是R樹了。