上一篇咱們大概瞭解了紅黑樹究竟是個什麼鬼,這篇咱們能夠看看另一種樹-----2-3-4樹,看這個樹的名字就以爲很奇怪。。。。html
咱們首先要知道這裏的二、三、4指的是任意一個節點擁有的子節點個數,因此咱們就大概知道2-3-4樹中的每個節點應該最多有四個子節點;注意:2-3-4樹中的任意一個節點不能只有一個子節點,應該只有幾種狀況:0、二、三、4java
有個東西一直忘記說了,就是那個大O表示法,或者叫作時間複雜度,感受最開始不要糾結於用這個大O表示法比較好,由於直接看這個你會以爲很蒙,學了必定的數據結構回頭再來看這個大O表示法,其實就那樣吧!下面就先簡單說說我我的對大O表示法的理解;node
1.大O表示法算法
記得之前初中學過一次函數,二次函數,y=kx,y=ax2編程
咱們通常是怎麼理解這兩種函數的,對於一次函數來講y與x成正比,二次函數也能夠說y與x2成正比,那麼咱們有沒有什麼簡單的表示方法呢?因而大O表示法就有做用了,在編程中咱們就能夠用O(N)和O(N2)這種方式來表示一次函數和二次函數。。。其實N就是至關於這裏的x數組
在算法中,上面提到的y表明運行某個算法所須要的時間,x一般表明數據的個數,k和a表明常數能夠不考慮,由於常數變化只會跟微處理器、編譯程序生成代碼的效率等因素有關;數據結構
提及來可能有點抽象,舉個很簡單的例子,假如咱們要遍歷包含10個元素的集合,此時遍歷所須要的時間就是y,N就是10,咱們遍歷所須要的時間是和集合中數據的數量成正比的,因而用大O表示法就是O(N);假如你要往一個無序數組中隨便插入一個數字,由於不管數組中有多少個元素,你都只須要一步就解決戰鬥,因此時間複雜度就是N(1);數據結構和算法
進一步說說O(N),這表示運行時間受數據項個數的影響的程度,也就是說括號中的值越小,運行時間受到的影響越小,效率就越高,一般括號中是都是關於數據項個數N的函數,好比O(N),O(N2),O(log2N)等,根據咱們高中學的數學知識畫一下經常使用的幾個函數圖像:編程語言
根據圖像可知咱們能夠知道O(1)最平緩,運行時間受到數據數量的影響最小,效率最高;O(N2)的效率最慢,下面咱們就簡單看看前面咱們實現的幾種數據結構的時間複雜度;ide
無序數組的插入:O(1),向插入一個數據直接插入就好,跟數組中有多少個數據無關
有序數組的插入:O(N),在向有序數組插入數據的時候,會和數組中的數據進行比較才能肯定插入的位置,很明顯和數組中的數據個數有關
無序數組的刪除、有序數組的刪除:O(N),刪除的話都跟數組中的數據個數有關,由於都是一個一個的遍歷到刪除的位置那裏,刪除數據
鏈表的插入和刪除:O(1)
鏈表查詢:O(N)
很平衡的搜索二叉樹查詢:O(log2N)
很平衡搜索二叉樹添加節點:O(log2N)
紅黑樹:全部操做都是O(log2N)
能夠看得出紅黑樹是一個幾乎很完美的數據結構了,各類操做效率都很高,可是全部的數據結構都有得必有失,提高效率的同時,該數據結構的內部邏輯就越複雜,兩者不可兼得;還有那些排序也能夠根據大O表示法看的出來其效率高低。。。後面有時間再說排序的東西;
2. 2-3-4樹的簡單介紹
什麼是2-3-4樹呢?就好比前面咱們說的搜索二叉樹、紅黑樹都是屬於二叉樹,每一個節點中只有一個數據,並且最多隻有兩個子節點;那能不能在節點中存多個數據呢?一個節點能夠有不少個子節點?因而就有了2-3-4樹,2-3-4樹屬於多叉樹,跟紅黑樹同樣是平衡的,效率比紅黑樹略低,可是編程容易不少,並且經過2-3-4樹咱們能夠更容易理解B樹,那有人就要問了,B樹是幹嗎的?暫時咱們就不要糾結這個,後面有時間天然會說到的。
不知道你們理解節點中的數據是怎麼看的,反正我是看成一個數軸來看的,就好比搜索二叉樹,我的感受根據這種方式更好理解節點中數據項和子節點數量的關係,嘿嘿!
那麼若是節點中的數據不止一個呢?其實就是把上面這個作一個簡單的變形就ok了,道理仍是同樣的,咱們的2-3-4樹中的二、三、4指的是除了葉節點以外,任意一個節點可能有的子節點的個數,換句話來講就是任意節點中存的數據最多爲3個
下面咱們看看畫的比較好看的2-3-4樹,能夠簡單的知道:節點的子節點個數=節點數據項+1
3. 2-3-4樹的各類操做
粗略的知道了這些以後咱們能夠簡單的分析一下各類操做的原理;
首先咱們能夠簡單想一想節點類裏面究竟是什麼屬性,首先應該有一個有序數組,裏面能夠按照必定的順序存放三個數據;而後還有一個數組,用於存放子節點的引用;還須要有一個變量指向父節點的引用,最好還有一個int變量標識當前節點中數據項的數目;
咱們用最簡單的來,節點中就存整數,並且爲了好寫代碼,咱們把節點中的空位置用0,1,2來標識一下,把每一個節點的子節點也用0,1,2,3,來標識一下;爲何不從1開始呢?由於節點中的數據和存放子節點的引用都是保存在數組中,數組都是從0開始的啊。。。。
因此節點類以下所示:
詳細的節點類代碼:
public static class Node{ //當前節點中存的數據個數 private int length; //當前節點存有父節點的引用 private Node parent; //當前節點中有三個空位置能夠存放數據 private Integer[] data = new Integer[3]; //每一個節點最多能夠有四個子節點,咱們準備四個位置隨時存放子節點引用 private Node[] childs = new Node[4]; //根據傳入子節點的爲索引,咱們將當前節點鏈接到子節點 public void connectNode(int childNum,Node child){ childs[childNum] = child; if (child!=null) { child.parent = this; } } //根據傳入的子節點索引,咱們斷開該子節點的鏈接,返回斷開的子節點 public Node cutNode(int nodeNum){ Node node = childs[nodeNum]; childs[nodeNum] = null; return node; } //獲取指定索引的子節點 public Node getChild(int nodeNum){ return childs[nodeNum]; } //獲取當前節點的父節點 public Node getParent(){ return this.parent; } //判斷當前節點是否是葉節點 public boolean isLeaf(){ if (childs[0]==null&&childs[1]==null&&childs[2]==null) { return true; } return false; } //獲取當前節點保存數據的個數 public int getLength(){ return this.length; } //判斷當前節點有沒有裝滿 public boolean isFull(){ return (length==3)?true:false; } //根據數據找到在節點中的位置索引 public int index(int value){ for (int i = 0; i < 3; i++) { if (data[i] == null) { break; }else if (value == data[i]) { return 1; } } return -1; } //將咱們的數據插入到節點中,其實這裏用到一個有序數組 //這裏的邏輯其實頗有意思,咱們是遍歷存數據的那個數組,從後往前,先找到非空的位置存放的數據key,和value比較,若是是value比較大, // 直接在key後面插入;若是value比較小,則將key日後挪一個位置,繼續循環往前遍歷,重複上面的步驟,直到value比該位置存放的數據大爲止,而後 // 直接插入到該位置後面便可; //假如通過for循環了還能往下執行,說明一直都是執行for循環中的第一個if中,換句話來講數組中數據都爲null,那就直接在數組索引爲0的位置插入value便可 public int insertToNode(int value){ length++; if (length>3) { return -1; } for(int i = 2 ; i >= 0 ; i--){ if(data[i] == null){ continue; }else{ int key = data[i]; if(value < key){ data[i+1] = data[i]; }else{ data[i+1] = value; return i+1; } } } //若是都爲空,或者都比待插入的數據項大,則將待插入的數據項放在節點第一個位置 data[0] = value; return 0; } //移除節點中最右端的數據 public int removeData(){ int temp = data[length-1]; data[length-1] = null; length--; return temp; } //打印當前節點中的全部數據,例如 /30/40/50/ public void displayNode(){ System.out.print("/"); for (int i = 0; i < length; i++) { System.out.print(data[i]+"/"); } System.out.println(""); } }
3.1.查詢操做
其實查詢操做很容易,相似搜索二叉樹,咱們在查詢一個數據在哪個節點的時候,仍是同樣首先和根節點中的數據比較(假設根節點有兩個數據10和30),若是比10小那就去第一個孩子節點那裏繼續去找;若是是比10大比30小,那就去第二個孩子那裏繼續找;若是比30大,那就去第三個孩子那裏接着找.....直到找到爲止;
代碼以下:
//去2-3-4樹中查找有沒有一個數字value public int find(int value){ Node current = root; int index ; //這裏一個無限循環,假如當前根節點有這個數據,那就返回1;假如當前只有一個根節點,尚未保存數據value,那就 //直接返回-1;假如根節點還有子節點,那就讓current這個指針指向下一個子節點,再重複上面的步驟 while(true){ if((index = current.index(value))!=-1){ return index; }else if(current.isLeaf()){//節點是葉節點 return -1; }else{ current = getNextChild(current,value); } } }
3.2 插入節點
這個也很容易,記住一點,插入節點始終都是在插入到葉節點中,也就是插入到最下面一層的節點中,可不會建立新的節點哦~這點和二叉樹有點不一樣!仔細一想也對,每一個節點中不是最多能夠有三個數據嗎,也就是有三個空位置可讓你插入數據,並且這三個空位置仍是有順序的;
咱們在插入數據的時候會首先檢查一下葉節點空位置有沒有滿,沒有滿的話就按照那個順序插入到合適的位置,滿了的話就要想辦法把這個滿了的節點拆開,變成幾個節點而後再進行插入數據就ok了!這個拆開節點的操做也叫作分裂,下面咱們會好好看看這個分裂究竟是什麼?也正是由於這個分裂才使得2-3-4樹保持了平衡;
代碼以下:
//插入數據項,其中這裏的循環是最重要的一個 public void insert(int value){ Node current = root; while(true){ //若是當前節點數據滿了,就分裂該節點,再把當前指針移動到合適的子節點那裏,而後跳出循環向當前節點添加數據 if(current.isFull()){ split(current);//分裂節點方法在下面 current = current.getParent(); current = getNextChild(current, value); //若是當前節點剛好是一個葉節點,直接跳出該循環,直接向當前節點添加數據 }else if(current.isLeaf()){ break; //若是當前節點既不是葉節點,也沒有裝滿,那就繼續進入該子節點 }else{ current = getNextChild(current, value); } } //向當前節點插入數據 current.insertToNode(value); }
3.3.節點分裂
節點分裂就是2-3-4樹爲了維護平衡所作的一些變化,和紅黑樹中的旋轉不一樣,因爲往2-3-4樹中插入數據只會插入到葉節點中,咱們來看看一個最簡單的插入操做。
什麼最簡單呢?就只有一個根節點的時候是最簡單的,我先把根節點裝滿以後繼續往添加節點,看看是怎樣變化,好比我插入節點30,50,80,10,以下圖所示:
這就是所謂的2-3-4樹全部的分裂了,記住,數據插入只能是在葉節點,假如該葉節點三個位置已經滿了,就要把這個節點的三個數據分開來放,一個數據在自己所在的節點,一個數據放到父節點,另外一個數據放到新建立的節點中。。。。。其實仍是挺有趣的吧!
固然咱們還能夠想一想上面最後一個圖中,當另外兩個子節點中的數據也滿了以後會分裂,分別會向父節點丟進去一個數據,此時根節點就滿了就會分裂,這個分裂就有點東西了,也是分裂最複雜的一種,看看下圖所示:
代碼:
//分裂節點,這個邏輯能夠說是最複雜的一個,我把大概的邏輯說一下: //首先把節點中數據項分別拆分紅三部分,一份仍是留給本身thisNode,一份是dataB,另一份是dataC //而後要新建一個兄弟節點newRight,還要改變當前節點thisNode的子節點引用(假如當前節點thisNode是根節點,那麼父節點也會變化), //以後就是將dataB和dataC插入到父節點和兄弟節點中,最後就是將原來的節點thisNode的全部子節點分配給thisNode和newRight public void split(Node thisNode){ Node parent,child2,child3; int dataIndex; int dataC = thisNode.removeData(); int dataB = thisNode.removeData(); child2 = thisNode.cutNode(2); child3 = thisNode.cutNode(3); Node newRight = new Node(); if(thisNode == root){//若是當前節點是根節點,執行根分裂 root = new Node(); parent = root; root.connectNode(0, thisNode); }else{ parent = thisNode.getParent(); } //處理父節點 dataIndex = parent.insertToNode(dataB); int n = parent.getLength(); for(int j = n-1; j > dataIndex ; j--){ Node temp = parent.cutNode(j); parent.connectNode(j+1, temp); } parent.connectNode(dataIndex+1, newRight); //處理新建的右節點 newRight.insertToNode(dataC); newRight.connectNode(0, child2); newRight.connectNode(1, child3); }
3.4 刪除
說實話,刪除這個操做的邏輯有點複雜,並且在《java數據結構和算法第二版》也沒有涉及到刪除操做,我本身查了一下相關的資料是這樣說的:須要處理節點的合併和調整,比較複雜,因爲沒有太大的必要,所以建議採用最簡單的作法:給刪除節點打標記,而後在業務處理時跳過便可。
然而我就是不信邪,我就要看看刪除的操做是什麼。。。。知道我真的看到了刪除的代碼,我就信邪了!表示暫時對刪除節點興趣不大。。。
想看看刪除操做的小夥伴,能夠參考這個大佬的博客:https://www.cnblogs.com/xzjxylophone/p/7542884.html
4.完整代碼
2-3-4樹完整代碼:
package com.wyq.test; public class My234Tree { //根節點,經過下面的構造器初始化一個根節點 private Node root; public My234Tree(){ root = new Node(); } //節點類 public static class Node{ //當前節點中存的數據個數 private int length; //當前節點存有父節點的引用 private Node parent; //當前節點中有三個空位置能夠存放數據 private Integer[] data = new Integer[3]; //每一個節點最多能夠有四個子節點,咱們準備四個位置隨時存放子節點引用 private Node[] childs = new Node[4]; //根據傳入子節點的爲索引,咱們將當前節點鏈接到子節點 public void connectNode(int childNum,Node child){ childs[childNum] = child; if (child!=null) { child.parent = this; } } //根據傳入的子節點索引,咱們斷開該子節點的鏈接,返回斷開的子節點 public Node cutNode(int nodeNum){ Node node = childs[nodeNum]; childs[nodeNum] = null; return node; } //獲取指定索引的子節點 public Node getChild(int nodeNum){ return childs[nodeNum]; } //獲取當前節點的父節點 public Node getParent(){ return this.parent; } //判斷當前節點是否是葉節點 public boolean isLeaf(){ if (childs[0]==null&&childs[1]==null&&childs[2]==null) { return true; } return false; } //獲取當前節點保存數據的個數 public int getLength(){ return this.length; } //判斷當前節點有沒有裝滿 public boolean isFull(){ return (length==3)?true:false; } //根據數據找到在節點中的位置索引 public int index(int value){ for (int i = 0; i < 3; i++) { if (data[i] == null) { break; }else if (value == data[i]) { return 1; } } return -1; } //將咱們的數據插入到節點中,其實這裏用到一個有序數組 //這裏的邏輯其實頗有意思,咱們是遍歷存數據的那個數組,從後往前,先找到非空的位置存放的數據key,和value比較,若是是value比較大, // 直接在key後面插入;若是value比較小,則將key日後挪一個位置,繼續循環往前遍歷,重複上面的步驟,直到value比該位置存放的數據大爲止,而後 // 直接插入到該位置後面便可; //假如通過for循環了還能往下執行,說明一直都是執行for循環中的第一個if中,換句話來講數組中數據都爲null,那就直接在數組索引爲0的位置插入value便可 public int insertToNode(int value){ length++; if (length>3) { return -1; } for(int i = 2 ; i >= 0 ; i--){ if(data[i] == null){ continue; }else{ int key = data[i]; if(value < key){ data[i+1] = data[i]; }else{ data[i+1] = value; return i+1; } } } //若是都爲空,或者都比待插入的數據項大,則將待插入的數據項放在節點第一個位置 data[0] = value; return 0; } //移除節點中最右端的數據 public int removeData(){ int temp = data[length-1]; data[length-1] = null; length--; return temp; } //打印當前節點中的全部數據,例如 /30/40/50/ public void displayNode(){ System.out.print("/"); for (int i = 0; i < length; i++) { System.out.print(data[i]+"/"); } System.out.println(""); } } //去2-3-4樹中查找有沒有一個數字value public int find(int value){ Node current = root; int index ; //這裏一個無限循環,假如當前根節點有這個數據,那就返回1;假如當前只有一個根節點,尚未保存數據value,那就 //直接返回-1;假如根節點還有子節點,那就讓current這個指針指向下一個子節點,再重複上面的步驟 while(true){ if((index = current.index(value))!=-1){ return index; }else if(current.isLeaf()){//節點是葉節點 return -1; }else{ current = getNextChild(current,value); } } } //怎麼進入到下一個子節點中呢?利用一個for循環遍歷當前節點中的數據,而後根據value是在哪個範圍裏面就對應哪個子節點 //這裏的for循環有點東西,能夠仔細看看 public Node getNextChild(Node node,int value){ int j; int dataNum = node.getLength(); for(j = 0 ; j < dataNum ; j++){ if(value<node.data[j]){ return node.getChild(j); } } return node.getChild(j); } //插入數據項,其中這裏的循環是最重要的一個 public void insert(int value){ Node current = root; while(true){ //若是當前節點數據滿了,就分裂該節點,再把當前指針移動到合適的子節點那裏,而後跳出循環向當前節點添加數據 if(current.isFull()){ split(current);//分裂節點方法在下面 current = current.getParent(); current = getNextChild(current, value); //若是當前節點剛好是一個葉節點,直接跳出該循環,直接向當前節點添加數據 }else if(current.isLeaf()){ break; //若是當前節點既不是葉節點,也沒有裝滿,那就繼續進入該子節點 }else{ current = getNextChild(current, value); } } //向當前節點插入數據 current.insertToNode(value); } //分裂節點,這個邏輯能夠說是最複雜的一個,我把大概的邏輯說一下: //首先把節點中數據項分別拆分紅三部分,一份仍是留給本身thisNode,一份是dataB,另一份是dataC //而後要新建一個兄弟節點newRight,還要改變當前節點thisNode的子節點引用(假如當前節點thisNode是根節點,那麼父節點也會變化), //以後就是將dataB和dataC插入到父節點和兄弟節點中,最後就是將原來的節點thisNode的全部子節點分配給thisNode和newRight public void split(Node thisNode){ Node parent,child2,child3; int dataIndex; int dataC = thisNode.removeData(); int dataB = thisNode.removeData(); child2 = thisNode.cutNode(2); child3 = thisNode.cutNode(3); Node newRight = new Node(); if(thisNode == root){//若是當前節點是根節點,執行根分裂 root = new Node(); parent = root; root.connectNode(0, thisNode); }else{ parent = thisNode.getParent(); } //處理父節點 dataIndex = parent.insertToNode(dataB); int n = parent.getLength(); for(int j = n-1; j > dataIndex ; j--){ Node temp = parent.cutNode(j); parent.connectNode(j+1, temp); } parent.connectNode(dataIndex+1, newRight); //處理新建的右節點 newRight.insertToNode(dataC); newRight.connectNode(0, child2); newRight.connectNode(1, child3); } //打印樹中全部節點 public void displayTree(){ recDisplayTree(root,0,0); } //這裏的level表示當前節點在樹中的層數;childNumber表示在當前節點屬於父節點的第幾個子節點 private void recDisplayTree(Node thisNode,int level,int childNumber){ System.out.println("levle="+level+" child="+childNumber+" "); thisNode.displayNode(); int numItems = thisNode.getLength(); for(int j = 0; j < numItems+1 ; j++){ Node nextNode = thisNode.getChild(j); if(nextNode != null){ recDisplayTree(nextNode, level+1, j); }else{ return; } } } public static void main(String[] args) { My234Tree tree = new My234Tree(); tree.insert(1); tree.insert(10); tree.insert(100); tree.insert(1111); tree.insert(14); tree.insert(18); tree.insert(132); tree.insert(16); tree.insert(15); tree.insert(1); tree.insert(10); tree.insert(100); tree.insert(1111); tree.insert(14); tree.insert(18); tree.insert(132); tree.insert(16); tree.insert(15); tree.displayTree(); int find = tree.find(99); System.out.println(find); } }
測試結果:
5. 2-3-4樹和紅黑樹
感受這個部分就瞭解一下便可,根據本身的須要能夠選擇看或者不看;
在歷史上,先發展出來的是2-3-4樹,而所謂的紅黑樹是在這個基礎上進一步發展才獲得的,那麼這兩種樹確定有着某種不可告人的祕密,那麼究竟是什麼祕密呢?
偷個懶,就不本身畫圖了,就隨便看看下面這兩個圖,一個是2-3-4樹,另外一個是紅黑樹,這兩個是等效的!
兩種樹看似徹底不同,其實真要提及來的話也差很少,咱們只須要經過某些規則就可使一個2-3-4樹轉化爲一個紅黑樹,雖然實際應用時確定不會這樣去轉化,瞭解一下仍是挺有趣的;
5.1簡單的看看一些規則(記住子節點都是紅色就ok了)
(1) 2-3-4樹的節點只有一個數據項的狀況
(2)2-3-4樹的節點只有兩個數據項的狀況
(3) 2-3-4樹的節點有三個數據項的狀況
基於上述三種規則就能夠將一個2-3-4樹變爲一個紅黑樹了,下面就隨意看看一個例子:
5.2 2-3-4樹和紅黑樹的等效操做
那麼就有人要問了,紅黑樹中有變化顏色和旋轉啊,2-3-4樹中有什麼操做是與之相對應的嗎?固然有啦,咱們能夠簡單的看看二者對應的關係:
紅黑樹中的顏色變換---------->2-3-4樹中節點分裂
紅黑樹中的左旋和右旋------------>2-3-4樹中選取哪一個數據做爲父節點,就像上面5.2那裏同樣
首先是對於2-3-4樹中的節點分裂應該就不用多說了吧,你把分裂前對應的紅黑樹畫出來,再把分裂後的紅黑樹畫出來,就能明顯的看出來:
而對於2-3-4樹中節點數據選擇哪個做爲父節點,就等效於紅黑樹的左旋右旋,下面圖中以80爲父節點的紅黑樹--------------------->以70爲父節點的紅黑樹,就要通過右旋;
5.3. 2-3-4樹和紅黑樹的效率
說過了大O表示法,咱們就簡單的來看看2-3-4樹和紅黑樹的小路,前面說過2-3-4樹查詢的效率比紅黑樹略低一點,爲何呢?
首先從速度方面來來看看,由於紅-黑樹的層數(平衡二叉樹)大約是log2(N+1),而2-3-4樹每一個節點能夠最多有4個數據項,若是節點都是滿的,那麼高度和log4N成正比。所以在全部節點都滿的狀況下,2-3-4樹的高度大體是紅-黑樹的一半。不過他們不可能都是滿的,因此2-3-4樹的高度大體在log2(N+1)和log2(N+1)/2,按理來講減小2-3-4樹的高度可使它的查找時間比紅-黑樹的短一些,但是2-3-4樹中每個節點的數據項變多了,這也會影響查詢時間;
2-3-4樹總的查找時間和M*log4N成正比,因爲樹中節點可能存一個數據項,兩個數據項,三個數據項,取平均數都按兩個算查找時間跟2*log4N成正比,在大O表示法中2這個常數能夠忽略不計,並且在2-3-4樹中每一個節點數據項增長了抵消了樹高度比較矮的優點,一增一減之下其實和紅黑樹差很少,都是O(logN),話說大O表示法中的logN是以2爲底數的,其實寫成lgN也無所謂,底數不一樣的對數能夠相互轉化的,無非是乘以一個常數而已,這就很少說了。。。
而後咱們從存儲需求的角度看看,2-3-4樹中的節點的數據項不可能填滿,咱們仔細說說大概利用率是多少!
一個節點中有兩個數組,這兩個數組的大小是肯定了的分別爲3和4,假如一個節點中存的數據只有一個,那麼就會浪費2/3的數據存儲空間和1/2的子節點存儲空間;假如節點中存的數據有兩個,數據存儲空間浪費1/3,子節點存儲空間浪費1/4;平均一下按照每個節點只有兩個數據項來算一下,2-3-4樹浪費了2/7的空間;反觀紅黑樹全部的能用到的存儲空間都用了,利用率就比2-3-4樹更高;因爲在java中的2-3-4樹中存儲的是對象的引用,因此這種效率還不是很明顯,在有的編程語言保存的不是對象的引用,那麼2-3-4樹和紅黑樹的存儲的效率差別就顯現出來了;