最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

帶你了解Java數(shù)據(jù)結(jié)構(gòu)和算法之2-3-4樹

 更新時間:2022年01月21日 14:43:35   作者:YSOcean  
這篇文章主要為大家介紹了Java數(shù)據(jù)結(jié)構(gòu)和算法之2-3-4樹,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助

1、2-3-4 樹介紹

2-3-4樹每個節(jié)點最多有四個字節(jié)點和三個數(shù)據(jù)項,名字中 2,3,4 的數(shù)字含義是指一個節(jié)點可能含有的子節(jié)點的個數(shù)。對于非葉節(jié)點有三種可能的情況:

①、有一個數(shù)據(jù)項的節(jié)點總是有兩個子節(jié)點;

②、有二個數(shù)據(jù)項的節(jié)點總是有三個子節(jié)點;

③、有三個數(shù)據(jù)項的節(jié)點總是有四個子節(jié)點;

簡而言之,非葉節(jié)點的子節(jié)點數(shù)總是比它含有的數(shù)據(jù)項多1。如果子節(jié)點個數(shù)為L,數(shù)據(jù)項個數(shù)為D,那么:L = D + 1

葉節(jié)點(上圖最下面的一排)是沒有子節(jié)點的,然而它可能含有一個、兩個或三個數(shù)據(jù)項??展?jié)點是不會存在的。

樹結(jié)構(gòu)中很重要的一點就是節(jié)點之間關(guān)鍵字值大小的關(guān)系。在二叉樹中,所有關(guān)鍵字值比某個節(jié)點值小的節(jié)點都在這個節(jié)點左子節(jié)點為根的子樹上;所有關(guān)鍵字值比某個節(jié)點值大的節(jié)點都在這個節(jié)點右子節(jié)點為根的子樹上。2-3-4 樹規(guī)則也是一樣,并且還加上以下幾點:

為了方便描述,用從0到2的數(shù)字給數(shù)據(jù)項編號,用0到3的數(shù)字給子節(jié)點編號,如下圖:

①、根是child0的子樹的所有子節(jié)點的關(guān)鍵字值小于key0;

②、根是child1的子樹的所有子節(jié)點的關(guān)鍵字值大于key0并且小于key1;

③、根是child2的子樹的所有子節(jié)點的關(guān)鍵字值大于key1并且小于key2;

④、根是child3的子樹的所有子節(jié)點的關(guān)鍵字值大于key2。

簡化關(guān)系如下圖,由于2-3-4樹中一般不允許出現(xiàn)重復(fù)關(guān)鍵值,所以不用考慮比較關(guān)鍵值相同的情況。

2、搜索2-3-4樹

查找特定關(guān)鍵字值的數(shù)據(jù)項和在二叉樹中的搜索類似。從根節(jié)點開始搜索,除非查找的關(guān)鍵字值就是根,否則選擇關(guān)鍵字值所在的合適范圍,轉(zhuǎn)向那個方向,直到找到為止。

比如對于下面這幅圖,我們需要查找關(guān)鍵字值為 64 的數(shù)據(jù)項。

首先從根節(jié)點開始,根節(jié)點只有一個數(shù)據(jù)項50,沒有找到,而且因為64比50大,那么轉(zhuǎn)到根節(jié)點的子節(jié)點child1。60|70|80 也沒有找到,而且60<64<70,所以我們還是找該節(jié)點的child1,62|64|66,我們發(fā)現(xiàn)其第二個數(shù)據(jù)項正好是64,于是找到了。

3、插入

新的數(shù)據(jù)項一般要插在葉節(jié)點里,在樹的最底層。如果你插入到有子節(jié)點的節(jié)點里,那么子節(jié)點的編號就要發(fā)生變化來維持樹的結(jié)構(gòu),因為在2-3-4樹中節(jié)點的子節(jié)點要比數(shù)據(jù)項多1。

插入操作有時比較簡單,有時卻很復(fù)雜。

①、當(dāng)插入沒有滿數(shù)據(jù)項的節(jié)點時是很簡單的,找到合適的位置,只需要把新數(shù)據(jù)項插入就可以了,插入可能會涉及到在一個節(jié)點中移動一個或其他兩個數(shù)據(jù)項,這樣在新的數(shù)據(jù)項插入后關(guān)鍵字值仍保持正確的順序。如下圖:

②、如果往下尋找插入位置的途中,節(jié)點已經(jīng)滿了,那么插入就變得復(fù)雜了。發(fā)生這種情況時,節(jié)點必須分裂,分裂能保證2-3-4樹的平衡。

ps:這里討論的是自頂向下的2-3-4樹,因為是在向下找到插入點的路途中節(jié)點發(fā)生了分裂。把要分裂的數(shù)據(jù)項設(shè)為A,B,C,下面是節(jié)點分裂的情況(假設(shè)分裂的節(jié)點不是根節(jié)點):

1、節(jié)點分裂

一、創(chuàng)建一個新的空節(jié)點,它是要分裂節(jié)點的兄弟,在要分裂節(jié)點的右邊;

二、數(shù)據(jù)項C移到新節(jié)點中;

三、數(shù)據(jù)項B移到要分裂節(jié)點的父節(jié)點中;

四、數(shù)據(jù)項A保留在原來的位置;

五、最右邊的兩個子節(jié)點從要分裂處斷開,連到新節(jié)點上。

上圖描述了節(jié)點分裂的例子,另一種描述節(jié)點分裂的說法是4-節(jié)點變成了兩個 2- 節(jié)點。節(jié)點分裂是把數(shù)據(jù)向上和向右移動,從而保持了數(shù)的平衡。一般插入只需要分裂一個節(jié)點,除非插入路徑上存在不止一個滿節(jié)點時,這種情況就需要多重分裂。

2、根的分裂

如果一開始查找插入節(jié)點時就碰到滿的根節(jié)點,那么插入過程更復(fù)雜:

①、創(chuàng)建新的根節(jié)點,它是要分裂節(jié)點的父節(jié)點。

②、創(chuàng)建第二個新的節(jié)點,它是要分裂節(jié)點的兄弟節(jié)點;

③、數(shù)據(jù)項C移到新的兄弟節(jié)點中;

④、數(shù)據(jù)項B移到新的根節(jié)點中;

⑤、數(shù)據(jù)項A保留在原來的位置;

⑥、要分裂節(jié)點最右邊的兩個子節(jié)點斷開連接,連到新的兄弟節(jié)點中。

上圖便是根分裂的情況,分裂完成之后,整個樹的高度加1。另外一種描述根分裂的方法是說4-節(jié)點變成三個2-節(jié)點。

注意:插入時,碰到?jīng)]有滿的節(jié)點時,要繼續(xù)向下尋找其子節(jié)點進(jìn)行插入。如果直接插入該節(jié)點,那么還要進(jìn)行子節(jié)點的增加,因為在2-3-4樹中節(jié)點的子節(jié)點個數(shù)要比數(shù)據(jù)項多1;如果插入的節(jié)點滿了,那么就要進(jìn)行節(jié)點分裂。下圖是一系列插入過程,有4個節(jié)點分裂了,兩個是根,兩個是葉節(jié)點:

4、完整源碼實現(xiàn)

分為節(jié)點類Node,表示每個節(jié)點的數(shù)據(jù)項類DataItem,以及最后的2-3-4樹類Tree234.class

package com.ys.tree.twothreefour;
public class Tree234 {
    private Node root = new Node() ;
    /*public Tree234(){
        root = new Node();
    }*/
    //查找關(guān)鍵字值
    public int find(long key){
        Node curNode = root;
        int childNumber ;
        while(true){
            if((childNumber = curNode.findItem(key))!=-1){
                return childNumber;
            }else if(curNode.isLeaf()){//節(jié)點是葉節(jié)點
                return -1;
            }else{
                curNode = getNextChild(curNode,key);
            }
        }
    }
    public Node getNextChild(Node theNode,long theValue){
        int j;
        int numItems = theNode.getNumItems();
        for(j = 0 ; j < numItems ; j++){
            if(theValue < theNode.getItem(j).dData){
                return theNode.getChild(j);
            }
        }
        return theNode.getChild(j);
    }
    //插入數(shù)據(jù)項
    public void insert(long dValue){
        Node curNode = root;
        DataItem tempItem = new DataItem(dValue);
        while(true){
            if(curNode.isFull()){//如果節(jié)點滿數(shù)據(jù)項了,則分裂節(jié)點
                split(curNode);
                curNode = curNode.getParent();
                curNode = getNextChild(curNode, dValue);
            }else if(curNode.isLeaf()){//當(dāng)前節(jié)點是葉節(jié)點
                break;
            }else{
                curNode = getNextChild(curNode, dValue);
            }
        }//end while
        curNode.insertItem(tempItem);
    }
    public void split(Node thisNode){
        DataItem itemB,itemC;
        Node parent,child2,child3;
        int itemIndex;
        itemC = thisNode.removeItem();
        itemB = thisNode.removeItem();
        child2 = thisNode.disconnectChild(2);
        child3 = thisNode.disconnectChild(3);
        Node newRight = new Node();
        if(thisNode == root){//如果當(dāng)前節(jié)點是根節(jié)點,執(zhí)行根分裂
            root = new Node();
            parent = root;
            root.connectChild(0, thisNode);
        }else{
            parent = thisNode.getParent();
        }
        //處理父節(jié)點
        itemIndex = parent.insertItem(itemB);
        int n = parent.getNumItems();
        for(int j = n-1; j > itemIndex ; j--){
            Node temp = parent.disconnectChild(j);
            parent.connectChild(j+1, temp);
        }
        parent.connectChild(itemIndex+1, newRight);
        //處理新建的右節(jié)點
        newRight.insertItem(itemC);
        newRight.connectChild(0, child2);
        newRight.connectChild(1, child3);
    }
    //打印樹節(jié)點
    public void displayTree(){
        recDisplayTree(root,0,0);
    }
    private void recDisplayTree(Node thisNode,int level,int childNumber){
        System.out.println("levle="+level+" child="+childNumber+" ");
        thisNode.displayNode();
        int numItems = thisNode.getNumItems();
        for(int j = 0; j < numItems+1 ; j++){
            Node nextNode = thisNode.getChild(j);
            if(nextNode != null){
                recDisplayTree(nextNode, level+1, j);
            }else{
                return;
            }
        }
    }
    //數(shù)據(jù)項
    class DataItem{
        public long dData;
        public DataItem(long dData){
            this.dData = dData;
        }
        public void displayItem(){
            System.out.println("/"+dData);
        }
    }
    //節(jié)點
    class Node{
        private static final int ORDER = 4;
        private int numItems;//表示該節(jié)點有多少個數(shù)據(jù)項
        private Node parent;//父節(jié)點
        private Node childArray[] = new Node[ORDER];//存儲子節(jié)點的數(shù)組,最多有4個子節(jié)點
        private DataItem itemArray[] = new DataItem[ORDER-1];//存放數(shù)據(jù)項的數(shù)組,一個節(jié)點最多有三個數(shù)據(jù)項
        //連接子節(jié)點
        public void connectChild(int childNum,Node child){
            childArray[childNum] = child;
            if(child != null){
                child.parent = this;
            }
        }
        //斷開與子節(jié)點的連接,并返回該子節(jié)點
        public Node disconnectChild(int childNum){
            Node tempNode = childArray[childNum];
            childArray[childNum] = null;
            return tempNode;
        }
        //得到節(jié)點的某個子節(jié)點
        public Node getChild(int childNum){
            return childArray[childNum];
        }
        //得到父節(jié)點
        public Node getParent(){
            return parent;
        }
        //判斷是否是葉節(jié)點
        public boolean isLeaf(){
            return (childArray[0] == null)?true:false;
        }
        //得到節(jié)點數(shù)據(jù)項的個數(shù)
        public int getNumItems(){
            return numItems;
        }
        //得到節(jié)點的某個數(shù)據(jù)項
        public DataItem getItem(int index){
            return itemArray[index];
        }
        //判斷節(jié)點的數(shù)據(jù)項是否滿了(最多3個)
        public boolean isFull(){
            return (numItems == ORDER-1) ? true:false;
        }
        //找到數(shù)據(jù)項在節(jié)點中的位置
        public int findItem(long key){
            for(int j = 0 ; j < ORDER-1 ; j++){
                if(itemArray[j]==null){
                    break;
                }else if(itemArray[j].dData == key){
                    return j;
                }
            }
            return -1;
        }
        //將數(shù)據(jù)項插入到節(jié)點
        public int insertItem(DataItem newItem){
            numItems++;
            long newKey = newItem.dData;
            for(int j = ORDER-2 ; j >= 0 ; j--){
                if(itemArray[j] == null){//如果為空,繼續(xù)向前循環(huán)
                    continue;
                }else{
                    long itsKey = itemArray[j].dData;//保存節(jié)點某個位置的數(shù)據(jù)項
                    if(newKey < itsKey){//如果比新插入的數(shù)據(jù)項大
                        itemArray[j+1] = itemArray[j];//將大數(shù)據(jù)項向后移動一位
                    }else{
                        itemArray[j+1] = newItem;//如果比新插入的數(shù)據(jù)項小,則直接插入
                        return j+1;
                    }
                }
            }
            //如果都為空,或者都比待插入的數(shù)據(jù)項大,則將待插入的數(shù)據(jù)項放在節(jié)點第一個位置
            itemArray[0] = newItem;
            return 0;
        }
        //移除節(jié)點的數(shù)據(jù)項
        public DataItem removeItem(){
            DataItem temp = itemArray[numItems-1];
            itemArray[numItems-1] = null;
            numItems--;
            return temp;
        }
        //打印節(jié)點的所有數(shù)據(jù)項
        public void displayNode(){
            for(int j = 0 ; j < numItems ; j++){
                itemArray[j].displayItem();
            }
            System.out.println("/");
        }
    }
}

5、2-3-4樹和紅黑樹  

2-3-4樹是多叉樹,而紅黑樹是二叉樹,看上去可能完全不同,但是,在某種意義上它們又是完全相同的,一個可以通過應(yīng)用一些簡單的規(guī)則變成另一個,而且使他們保持平衡的操作也是一樣,數(shù)學(xué)上稱他們?yōu)橥瑯?gòu)。

①、對應(yīng)規(guī)則

應(yīng)用如下三條規(guī)則可以將2-3-4樹轉(zhuǎn)化為紅黑樹:

一、把2-3-4樹中的每個2-節(jié)點轉(zhuǎn)化為紅-黑樹的黑色節(jié)點。

二、把每個3-節(jié)點轉(zhuǎn)化為一個子節(jié)點和一個父節(jié)點,子節(jié)點有兩個自己的子節(jié)點:W和X或X和Y。父節(jié)點有另一個子節(jié)點:Y或W。哪個節(jié)點變成子節(jié)點或父節(jié)點都無所謂。子節(jié)點涂成紅色,父節(jié)點涂成黑色。

三、把每個4-節(jié)點轉(zhuǎn)化為一個父節(jié)點和兩個子節(jié)點。第一個子節(jié)點有它自己的子節(jié)點W和X;第二個子節(jié)點擁有子節(jié)點Y和Z。和前面一樣,子節(jié)點涂成紅色,父節(jié)點涂成黑色。

下圖是一顆2-3-4樹轉(zhuǎn)化成對應(yīng)的紅-黑樹。虛線環(huán)繞的子樹是由3-節(jié)點和4-節(jié)點變成的。轉(zhuǎn)化后符合紅-黑樹的規(guī)則,根節(jié)點為紅色,兩個紅色節(jié)點不會相連,每條從根到葉節(jié)點的路徑上的黑節(jié)點個數(shù)是一樣的?! ?/p>

②、操作等價

不僅紅-黑樹的結(jié)構(gòu)與2-3-4樹對應(yīng),而且兩種樹操作也一樣。2-3-4樹用節(jié)點分裂保持平衡,紅-黑樹用顏色變換和旋轉(zhuǎn)保持平衡。

上圖是4-節(jié)點分裂。虛線環(huán)繞的部分等價于4-節(jié)點。顏色變換之后,40,60節(jié)點都為黑色的,50節(jié)點是紅色的。因此,節(jié)點 50 和它的父節(jié)點70 對于3-節(jié)點,如上圖虛線所示。

6、2-3-4 樹的效率

分析2-3-4樹我們可以和紅黑樹作比較分析。紅-黑樹的層數(shù)(平衡二叉樹)大約是log2(N+1),而2-3-4樹每個節(jié)點可以最多有4個數(shù)據(jù)項,如果節(jié)點都是滿的,那么高度和log4N。因此在所有節(jié)點都滿的情況下,2-3-4樹的高度大致是紅-黑樹的一半。不過他們不可能都是滿的,所以2-3-4樹的高度大致在log2(N+1)和log2(N+1)/2。減少2-3-4樹的高度可以使它的查找時間比紅-黑樹的短一些。

但是另一方面,每個節(jié)點要查看的數(shù)據(jù)項就多了,這會增加查找時間。因為節(jié)點中用線性搜索來查看數(shù)據(jù)項,使得查找時間的倍數(shù)和M成正比,即每個節(jié)點數(shù)據(jù)項的平均數(shù)量??偟牟檎視r間和M*log4N成正比。

總結(jié)

本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!

相關(guān)文章

  • MyBatis中的resultMap簡要概述

    MyBatis中的resultMap簡要概述

    這篇文章主要介紹了MyBatis中的resultMap簡要概述的相關(guān)資料,需要的朋友可以參考下
    2016-07-07
  • Java實戰(zhàn)房屋租賃網(wǎng)的實現(xiàn)流程

    Java實戰(zhàn)房屋租賃網(wǎng)的實現(xiàn)流程

    讀萬卷書不如行萬里路,只學(xué)書上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+SSM+jsp+mysql+maven實現(xiàn)一個房屋租賃網(wǎng)站,大家可以在過程中查缺補(bǔ)漏,提升水平
    2021-11-11
  • SpringBoot詳細(xì)分析自動裝配原理并實現(xiàn)starter

    SpringBoot詳細(xì)分析自動裝配原理并實現(xiàn)starter

    相對于傳統(tǒng)意義上的Spring項目,SpringBoot具有開箱即用,簡化配置,內(nèi)置Tomcat等等等等一系列的特點。在這些特點中,最重要的兩條就是約定優(yōu)于配置和自動裝配
    2022-07-07
  • java獲取一個文本文件的編碼(格式)信息

    java獲取一個文本文件的編碼(格式)信息

    這篇文章主要介紹了java獲取一個文本文件的編碼(格式)信息,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下
    2022-09-09
  • JVM執(zhí)行引擎的項目實踐

    JVM執(zhí)行引擎的項目實踐

    執(zhí)行引擎是Java虛擬機(jī)核心的組成部分之一,本文主要介紹了JVM執(zhí)行引擎的項目實踐,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-04-04
  • Linux系統(tǒng)Docker拉取JDK21鏡像超細(xì)詳解

    Linux系統(tǒng)Docker拉取JDK21鏡像超細(xì)詳解

    這篇文章主要介紹了如何在Linux系統(tǒng)上使用Docker拉取JDK21鏡像,并基于該鏡像搭建開發(fā)和測試環(huán)境,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-12-12
  • SpringBoot如何使用Undertow做服務(wù)器

    SpringBoot如何使用Undertow做服務(wù)器

    這篇文章主要介紹了SpringBoot如何使用Undertow做服務(wù)器,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-07-07
  • Java?8?Time?Api?使用方法技巧

    Java?8?Time?Api?使用方法技巧

    這篇文章主要介紹了Java?8?Time?Api?使用方法技巧,Java?8為Date和Time引入了新的API,以解決舊java.util.Date和java.util.Calendar的缺點,更多相關(guān)內(nèi)容需要的小伙伴可以參考一下
    2022-05-05
  • 詳解IDEA多module項目maven依賴的一些說明

    詳解IDEA多module項目maven依賴的一些說明

    這篇文章主要介紹了詳解IDEA多module項目maven依賴的一些說明,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-10-10
  • springboot+mybatis+redis 二級緩存問題實例詳解

    springboot+mybatis+redis 二級緩存問題實例詳解

    Mybatis默認(rèn)沒有開啟二級緩存,需要在全局配置(mybatis-config.xml)中開啟二級緩存。本文講述的是使用Redis作為緩存,與springboot、mybatis進(jìn)行集成的方法。需要的朋友參考下吧
    2017-12-12

最新評論

阿拉善右旗| 乌兰浩特市| 平原县| 永靖县| 东阿县| 宜川县| 平安县| 五峰| 正定县| 丹阳市| 大田县| 明溪县| 铜鼓县| 三门县| 巴林左旗| 漠河县| 繁峙县| 孟村| 娄烦县| 关岭| 农安县| 时尚| 巴中市| 平乐县| 兴义市| 郸城县| 兴化市| 无为县| 冷水江市| 玉环县| 和政县| 修武县| 赣州市| 吉木萨尔县| 宾川县| 太康县| 乌鲁木齐县| 海南省| 永泰县| 盈江县| 土默特左旗|