樹結(jié)構(gòu)合集二

AA樹

紅黑樹的編程相當(dāng)復(fù)雜,AA樹是一顆帶有條件的紅黑樹,相當(dāng)情況下簡化了紅黑樹的編程.

  1. AA樹規(guī)定只有右孩子才能是紅色節(jié)點(diǎn).
  2. 我們不再記錄顏色,而是用level和每個(gè)節(jié)點(diǎn)存在一起.level實(shí)際上相當(dāng)與紅黑樹的black-height.
  3. 紅節(jié)點(diǎn)的level與其父節(jié)點(diǎn)相同
  4. 黑節(jié)點(diǎn)的level比其父節(jié)點(diǎn)低1
  5. 樹葉節(jié)點(diǎn)level為1

我們把顏色轉(zhuǎn)化為層次來看,可以發(fā)現(xiàn)左兒子必然比它的父節(jié)點(diǎn)恰好低1,右兒子可能比父節(jié)點(diǎn)低0或1.
水平鏈接出現(xiàn)在一個(gè)節(jié)點(diǎn)與相等層次的兒子之間的連接,由上面那些性質(zhì)我們可以發(fā)現(xiàn),水平鏈接一般都是向右的.

23

我們在編程的時(shí)候,插入點(diǎn)的時(shí)候通通把level設(shè)為1,也就是說與父節(jié)點(diǎn)都是水平鏈接,這樣會(huì)出現(xiàn)兩種不允許的情況:

  1. 水平左鏈接,這違反了AA樹自身的條件.
  2. 連續(xù)右鏈接,這相當(dāng)于違反了紅黑樹中紅節(jié)點(diǎn)的孩子不能也是紅的.

對于上面兩種情況,我們可以相應(yīng)采用skew方法以及split方法處理.

24

上圖可知skew與split分別對應(yīng)右旋轉(zhuǎn)與左旋轉(zhuǎn)(紅黑樹術(shù)語),并且注意R的層次在split中增加1.
需要注意的是,skew方法結(jié)束后很經(jīng)常會(huì)引起連續(xù)右鏈接,還需要通過split方法處理,所以一般我們先用skew再用split.
插入的過程就是不斷skew以及split了,刪除操作相對稍復(fù)雜一些.
由于我們采用遞歸刪除,所以在最后一層,如果不是葉子節(jié)點(diǎn)的話就沒問題,把右孩子賦給自身就可以,如果是葉子節(jié)點(diǎn)的話就會(huì)出現(xiàn)異常.但是考慮到遞歸屬性,我們?nèi)匀话延液⒆淤x給自身即nullNode.然后遞歸回去的時(shí)候進(jìn)行檢驗(yàn),如果右孩子或者左孩子比自身低2層,說明出現(xiàn)問題了,需要修復(fù).
修復(fù)的過程還是比較麻煩的,如果有右子樹,右子樹也要降低一層.修復(fù)可能會(huì)引起大量的連鎖反應(yīng),舉個(gè)例子:
25

如果我們要?jiǎng)h除1,那么遞歸到2時(shí)候發(fā)現(xiàn)左節(jié)點(diǎn)低2層,開始修復(fù).把2和5都降低一層,那么首先會(huì)出現(xiàn)左鏈接,5-3,修復(fù)完以后出現(xiàn)新的左鏈接5-4.另外考慮如果刪除在右邊,那么左節(jié)點(diǎn)也有可能水平,所以我們調(diào)用三次skew.接著有很多的連續(xù)右連接,我們只需要調(diào)用兩次split就可以重新處理好了.
編碼相對紅黑樹來說簡單太多:

  package com.fredal.structure;
public class AATree<AnyType extends Comparable<? super AnyType>>
{
    //內(nèi)部節(jié)點(diǎn)類
   private static class AANode<AnyType>
   {

       AANode( AnyType theElement, AANode<AnyType> lt, AANode<AnyType> rt )
       {
           element = theElement;
           left    = lt;
           right   = rt;
           level   = 1;
       }

       AnyType         element;      
       AANode<AnyType> left;        
       AANode<AnyType> right;        
       int             level;        // 層次
   }
   
   private AANode<AnyType> root;
   private AANode<AnyType> nullNode;
   
   private AANode<AnyType> deletedNode;
   private AANode<AnyType> lastNode;
   
   public AATree( )
   {
       nullNode = new AANode<AnyType>( null, null, null );
       nullNode.left = nullNode.right = nullNode;
       nullNode.level = 0;
       root = nullNode;
   }

   public void insert( AnyType x )
   {
       root = insert( x, root );
   }
   public void remove( AnyType x )
   {
       deletedNode = nullNode;
       root = remove( x, root );
   }
   //最小值
   public AnyType findMin( )
   {
       if( isEmpty( ) )
           return null;

       AANode<AnyType> ptr = root;

       while( ptr.left != nullNode )
           ptr = ptr.left;

       return ptr.element;
   }
   //最大值
   public AnyType findMax( )
   {
       if( isEmpty( ) )
           return null;

       AANode<AnyType> ptr = root;

       while( ptr.right != nullNode )
           ptr = ptr.right;

       return ptr.element;
   }
   //尋找值并返回
   public AnyType find( AnyType x )
   {
       AANode<AnyType> current = root;
       nullNode.element = x;

       for( ; ; )
       {
           if( x.compareTo( current.element ) < 0 )
               current = current.left;
           else if( x.compareTo( current.element ) > 0 ) 
               current = current.right;
           else if( current != nullNode )
               return current.element;
           else
               return null;
       }
   }
   //情空
   public void makeEmpty( )
   {
       root = nullNode;
   }
   //判空
   public boolean isEmpty( )
   {
       return root == nullNode;
   }
   //插入
   private AANode<AnyType> insert( AnyType x, AANode<AnyType> t )
   {
       if( t == nullNode )
           t = new AANode<AnyType>( x, nullNode, nullNode );//初始化
       else if( x.compareTo( t.element ) < 0 )
           t.left = insert( x, t.left );
       else if( x.compareTo( t.element ) > 0 )
           t.right = insert( x, t.right );
       else
           ;//重復(fù)值
       t = skew( t );//先skew 再split
       t = split( t );
       return t;
   }
   //刪除
   private AANode<AnyType> remove( AnyType x, AANode<AnyType> t )
   {
       if( t != nullNode )
       {
           // 往下搜索并設(shè)置lastnode和deletenode
           lastNode = t;
           if( x.compareTo( t.element ) < 0 )
               t.left = remove( x, t.left );
           else
           {
               deletedNode =  t;//要?jiǎng)h除的值
               t.right = remove( x, t.right );//找右子樹最小值
           }

           //在底層 直接刪除
           if( t==lastNode)
           {
               if( deletedNode == nullNode || x.compareTo( deletedNode.element ) != 0 )
                   throw new RuntimeException( x.toString( ) );
               deletedNode.element = t.element;
               t = t.right;//必定有右孩子
           }
           //不在底層 需要重新平衡
           else
               if( t.left.level < t.level - 1 || t.right.level < t.level - 1 )//低兩層 因?yàn)橛衝ullNode
               {
                   if( t.right.level > --t.level )//右子樹降一層
                       t.right.level = t.level;
                   t = skew( t );//三次skew
                   t.right = skew( t.right );
                   t.right.right = skew( t.right.right );
                   t = split( t );//兩次split
                   t.right = split( t.right );
               }
       }
       return t;
   }

   private static <AnyType> AANode<AnyType> skew( AANode<AnyType> t )
   {
       if( t.left.level == t.level )
           t = rotateWithLeftChild( t );
       return t;
   }
   
   private static <AnyType> AANode<AnyType> split( AANode<AnyType> t )
   {
       if( t.right.right.level == t.level )
       {
           t = rotateWithRightChild( t );
           t.level++;
       }
       return t;
   }

   private static <AnyType> AANode<AnyType> rotateWithLeftChild( AANode<AnyType> k2 )
   {
       AANode<AnyType> k1 = k2.left;
       k2.left = k1.right;
       k1.right = k2;
       return k1;
   }

   private static <AnyType> AANode<AnyType> rotateWithRightChild( AANode<AnyType> k1 )
   {
       AANode<AnyType> k2 = k1.right;
       k1.right = k2.left;
       k2.left = k1;
       return k2;
   }  

   public static void main( String [ ] args )
   {
       AATree<Integer> tree=new AATree<Integer>();
       final int NUM=40000;
       System.out.println("如果程序不輸出就成功了....");
       for(int i=37;i!=0;i=(i+37)%NUM)
           tree.insert(i);//偽隨機(jī)
       for(int i=1;i<NUM;i+=2)
           tree.remove(i);//刪除所有奇數(shù)
       if( tree.findMin( ) != 2 || tree.findMax( ) != NUM - 2 )
           System.out.println( "最大值最小值尋找錯(cuò)誤!" );

       for( int i = 2; i < NUM; i += 2 )
           if( tree.find(i)!=i )
               System.out.println( "偶數(shù)缺失" + i );

       for( int i = 1; i < NUM; i += 2 )
           if( tree.find(i)!=null)
               System.out.println( "奇數(shù)多余" + i );
   }
}

treap樹

treap樹是一種二叉查找樹,但是十分簡單.顧名思義我們可以知道,它是堆和樹的合體,在樹中維護(hù)了一個(gè)"優(yōu)先級",而優(yōu)先級具有堆性質(zhì).
采用隨機(jī)數(shù)的方式為"優(yōu)先級"賦值,并且滿足堆性質(zhì),而同時(shí)節(jié)點(diǎn)本身的值滿足二叉查找樹性質(zhì).

26

插入:對于插入,就像二叉查找樹一樣插入就好,但由于我們的優(yōu)先級是隨機(jī)數(shù)的,所以可能不會(huì)滿足堆性質(zhì).所以需要靠旋轉(zhuǎn)來實(shí)現(xiàn).
對于AVL樹玩的溜的話,下面兩種情況不成問題,不贅述了.
27

28

刪除:這里可以采取普通二叉查找樹的方式刪除,即右子樹找最小或者左子樹找最大,賦值時(shí)候不帶優(yōu)先級即可.
我們講另外一種通過旋轉(zhuǎn)的方式實(shí)現(xiàn):
葉子節(jié)點(diǎn)和單孩子不贅述,如果是雙孩子,就想辦法通過旋轉(zhuǎn)來達(dá)到單孩子即可.
29

這里刪除我們?nèi)匀徊捎眠f歸,所以刪除的時(shí)候不是直接判斷是否雙孩子還是單孩子的,而是通過遞歸的性質(zhì)來刪除,注意代碼里小技巧.
實(shí)現(xiàn)(隨機(jī)數(shù)產(chǎn)生器使用自己之前寫的,參考隨機(jī)數(shù))

  package com.fredal.structure;
public class TreapTree<T extends Comparable<? super T>> {

    //內(nèi)部節(jié)點(diǎn)類
    static class TreapNode<T>{
        T element;
        TreapNode<T> left;
        TreapNode<T> right;
        int priority;//優(yōu)先級
        
        private static Random random=new Random();//隨機(jī)數(shù)產(chǎn)生器

        TreapNode(T element, TreapNode<T> left, TreapNode<T> right) {
            this.element = element;
            this.left = left;
            this.right = right;
            this.priority = random.RandomInt();//隨機(jī)產(chǎn)生
        }
        
        TreapNode(T element){
            this(element, null, null);
        }
        
    }
    private TreapNode<T> root;
    private TreapNode<T> nullNode;
    
    public TreapTree(){
        nullNode=new TreapNode<T>(null);
        nullNode.left=nullNode.right=nullNode;
        nullNode.priority=Integer.MAX_VALUE;//設(shè)為無限大
        root=nullNode;
    }
    
    public void insert(T x){
        root=insert(x,root);
    }
    //插入操作
    private TreapNode<T> insert(T x, TreapNode<T> t) {
        if(t==nullNode)
            return new TreapNode<T>(x, nullNode, nullNode);
        int res=x.compareTo(t.element);
        if(res<0){
            t.left=insert(x, t.left);
            if(t.left.priority<t.priority)//左左旋轉(zhuǎn)
                t=rotateL(t);
        }else if(res>0){
            t.right=insert(x, t.right);
            if(t.right.priority<t.priority)
                t=rotateR(t);
        }
        return t;
    }
    
    // 右右單旋轉(zhuǎn)
    private static TreapNode rotateR(TreapNode k1) {
        TreapNode k2 = k1.right;
        k1.right = k2.left;
        k2.left = k1;
        return k2;
    }

    // 左左單旋轉(zhuǎn)
    private static TreapNode rotateL(TreapNode k2) {
        TreapNode k1 = k2.left;
        k2.left = k1.right;
        k1.right = k2;
        return k1;
    }

    public void remove(T x){
        root=remove(x,root);
    }
    private TreapNode<T> remove(T x, TreapNode<T> t) {
        if(t!=nullNode){
            int res=x.compareTo(t.element);
            
            if(res<0)
                t.left=remove(x, t.left);
            else if(res>0)
                t.right=remove(x, t.right);
            else{
                //找到了
                if(t.left.priority<t.right.priority)
                    t=rotateL(t);//左左旋轉(zhuǎn)
                else
                    t=rotateR(t);//右右旋轉(zhuǎn)
                if(t!=nullNode)
                    t=remove(x, t);//繼續(xù)遞歸
                else
                    t.left=nullNode;//旋轉(zhuǎn)后剛好等于刪除節(jié)點(diǎn)
            }
        }
        return t;
    }

    //最小值
    public T findMin(){
        if(isEmpty())
            throw new RuntimeException("空樹");
        TreapNode<T> ptr=root;
        while(ptr.left!=nullNode)
            ptr=ptr.left;
        return ptr.element;
    }
    //最大值
    public T findMax(){
        if(isEmpty())
            throw new RuntimeException("空樹");
        TreapNode<T> ptr=root;
        while(ptr.right!=nullNode)
            ptr=ptr.right;
        return ptr.element;
    }
    //是否存在
    public boolean contains(T x){
        TreapNode<T> current=root;
        nullNode.element=x;
        for(;;){
            int res=x.compareTo(current.element);
            if(res<0)
                current=current.left;
            else if(res>0)
                current=current.right;
            else
                return current!=nullNode;
        }
    }
    //情空
    public void makeEmpty(){
        root=nullNode;
    }
    //判空
    public boolean isEmpty(){
        return root==nullNode;
    }
    //打印
    public void printTree(){
        if(isEmpty())
            System.out.println("這是空樹");
        else
            printTree(root);
    }
    
    private void printTree(TreapNode<T> t) {
        if(t!=nullNode){
            printTree(t.left);
            System.out.println(t.element.toString());
            printTree(t.right);
        }
    }
    
    public static void main(String[] args) {
        TreapTree<Integer> tree=new TreapTree<Integer>();
        tree.insert(6);
        tree.insert(10);
        tree.insert(8);
        tree.insert(12);
        tree.insert(14);
        tree.insert(16);
        tree.printTree();
        tree.makeEmpty();
        
        final int NUM=40000;
        System.out.println("下面如果程序不輸出就成功了....");
        for(int i=37;i!=0;i=(i+37)%NUM)
            tree.insert(i);//偽隨機(jī)
        for(int i=1;i<NUM;i+=2)
            tree.remove(i);//刪除所有奇數(shù)
        if( tree.findMin( ) != 2 || tree.findMax( ) != NUM - 2 )
            System.out.println( "最大值最小值尋找錯(cuò)誤!" );

        for( int i = 2; i < NUM; i += 2 )
            if( !tree.contains(i))
                System.out.println( "偶數(shù)缺失" + i );

        for( int i = 1; i < NUM; i += 2 )
            if( tree.contains(i))
                System.out.println( "奇數(shù)多余" + i );
    }
}

k-d樹

如果要篩選出年齡30到35歲之間并且年薪在20萬到50萬的人士,這種問題叫做二維范圍查找.當(dāng)然相應(yīng)地還有k維.
我們可以使2-d樹(k-d樹)來解決問題,它的性質(zhì)如下.
在奇數(shù)層上的分支按照第一個(gè)關(guān)鍵字進(jìn)行二叉排序.在偶數(shù)層上按照第二個(gè)關(guān)鍵字進(jìn)行排序.
性質(zhì)非常簡單,編碼也非常簡單.注意如果關(guān)鍵字重復(fù)了,我們默認(rèn)把它放到右子樹,如果重復(fù)太多顯然是比較壞的情況.
k-d樹最壞情況是O(N),不像二叉查找樹那樣有紅黑樹等等最壞情況(O log N)的變種,因?yàn)樾D(zhuǎn)在這行不通.
k近鄰搜索是關(guān)于k-d樹的經(jīng)典的例子,首先通過二叉樹搜索(比較待查詢節(jié)點(diǎn)和分裂節(jié)點(diǎn)的分裂維的值笼恰,小于等于就進(jìn)入左子樹分支猫妙,等于就進(jìn)入右子樹分支直到葉子結(jié)點(diǎn)),順著“搜索路徑”很快能找到最近鄰的近似點(diǎn)倦畅,也就是與待查詢點(diǎn)處于同一個(gè)子空間的葉子結(jié)點(diǎn)驰弄;然后再回溯搜索路徑蝠筑,并判斷搜索路徑上的結(jié)點(diǎn)的其他子結(jié)點(diǎn)空間中是否可能有距離查詢點(diǎn)更近的數(shù)據(jù)點(diǎn),如果有可能揩懒,則需要跳到其他子結(jié)點(diǎn)空間中去搜索(將其他子結(jié)點(diǎn)加入到搜索路徑)。重復(fù)這個(gè)過程直到搜索路徑為空挽封。我們在這不做代碼演示.
給出k-d樹,應(yīng)該說2-d數(shù)基本的插入以及篩選范圍的代碼實(shí)例

  package com.fredal.structure;
//2-d樹
public class KDTree<T extends Comparable<? super T>> {
    private static class KDNode<T>{
        T[] data;//數(shù)組形式
        KDNode<T> left;
        KDNode<T> right;
        
        KDNode(T item[]){
            data=(T[]) new Comparable[2];
            data[0]=item[0];
            data[1]=item[1];
            left=right=null;
        }
    }    
    
    private KDNode<T> root;//根節(jié)點(diǎn)
    
    public KDTree(){
        root=null;
    }
    
    public void insert(T[] x){
        root=insert(x,root,0);
    }
    //插入
    private KDNode<T> insert(T[] x, KDNode<T> t, int level) {
        if(t==null)
            t=new KDNode<T>(x);
        else if(x[level].compareTo(t.data[level])<0)
            t.left=insert(x, t.left, 1-level);//每層關(guān)鍵字更替
        else//相同的話都?xì)w到右子樹
            t.right=insert(x, t.right, 1-level);
        return t;
    }
    
    public void printRange(T[] low,T[] high){
        printRange(low,high,root,0);
    }
    //篩選方法
    private void printRange(T[] low, T[] high, KDNode<T> t, int level) {
        if(t!=null){
            if(low[0].compareTo(t.data[0])<=0&&
                    low[1].compareTo(t.data[1])<=0&&
                    high[0].compareTo(t.data[0])>=0&&
                    high[1].compareTo(t.data[1])>=0)
                System.out.println(t.data[0]+","+t.data[1]);//輸出
            if(low[level].compareTo(t.data[level])<=0)
                printRange(low, high, t.left, 1-level);//下界 下一層
            if(high[level].compareTo(t.data[level])>=0)
                printRange(low, high, t.right, 1-level);//上界 下一層
        }
    }
    
    public static void main(String[] args) {
        KDTree<Integer> t=new KDTree<Integer>();
        for(int i=300;i<370;i++){
            Integer[] it=new Integer[2];
            it[0]=i;
            it[1]=2500-i;
            t.insert(it);
        }
        
        Integer[] low={300,2100};
        Integer[] high={350,2200};
        
        t.printRange(low, high);
    }
}

B樹

在大規(guī)模存儲(chǔ)數(shù)據(jù)的時(shí)候,往往樹深度過深而導(dǎo)致性能不佳,那么顯然需要想辦法減少深度,一個(gè)自然的想法即是一個(gè)節(jié)點(diǎn)有多個(gè)孩子,類似多叉樹這樣.
B樹是為了磁盤或其它存儲(chǔ)設(shè)備而設(shè)計(jì)的一種多叉(下面你會(huì)看到已球,相對于二叉臣镣,B樹每個(gè)內(nèi)結(jié)點(diǎn)有多個(gè)分支,即多叉)平衡查找樹,我們這里暫且不討論B+,B*等.
我們可以用階段定義B樹,也可以用度定義B樹,以度為例子,一顆度為m的B樹:

  1. 每個(gè)非根的內(nèi)結(jié)點(diǎn)至多有m個(gè)子女智亮,每個(gè)非根的結(jié)點(diǎn)必須至少含有m-1個(gè)關(guān)鍵字忆某,如果樹是非空的,則根結(jié)點(diǎn)至少包含一個(gè)關(guān)鍵字阔蛉;
  2. 每個(gè)結(jié)點(diǎn)可包含至多2m-1個(gè)關(guān)鍵字弃舒。所以一個(gè)內(nèi)結(jié)點(diǎn)至多可有2m個(gè)子女。如果一個(gè)結(jié)點(diǎn)恰好有2m-1個(gè)關(guān)鍵字状原,我們就說這個(gè)結(jié)點(diǎn)是滿的

插入:插入相對來說比較簡單

  1. 利用前述的B-樹的查找算法查找關(guān)鍵字的插入位置聋呢。若找到,則說明該關(guān)鍵字已經(jīng)存在颠区,直接返回削锰。否則查找操作必失敗于某個(gè)最低層的非終端結(jié)點(diǎn)上。
  2. 判斷該結(jié)點(diǎn)是否還有空位置毕莱。即判斷該結(jié)點(diǎn)的關(guān)鍵字總數(shù)是否滿足n<=m-1器贩。若滿足,則說明該結(jié)點(diǎn)還有空位置朋截,直接把關(guān)鍵字k插入到該結(jié)點(diǎn)的合適位置上蛹稍。若不滿足,說明該結(jié)點(diǎn)己沒有空位置部服,需要把結(jié)點(diǎn)分裂成兩個(gè)唆姐。

分裂的方法是:生成一新結(jié)點(diǎn)。把原結(jié)點(diǎn)上的關(guān)鍵字和k按升序排序后饲宿,從中間位置把關(guān)鍵字(不包括中間位置的關(guān)鍵字)分成兩部分厦酬。左部分所含關(guān)鍵字放在舊結(jié)點(diǎn)中,右部分所含關(guān)鍵字放在新結(jié)點(diǎn)中瘫想,中間位置的關(guān)鍵字連同新結(jié)點(diǎn)的存儲(chǔ)位置插入到父結(jié)點(diǎn)中仗阅。如果父結(jié)點(diǎn)的關(guān)鍵字個(gè)數(shù)也超過(m-1),則要再分裂国夜,再往上插减噪。直至這個(gè)過程傳到根結(jié)點(diǎn)為止。

30

31

32

33

刪除:在B-樹上刪除關(guān)鍵字k的過程分兩步完成:

  1. 利用B-樹的查找算法找出該關(guān)鍵字所在的結(jié)點(diǎn)车吹。然后根據(jù) k所在結(jié)點(diǎn)是否為葉子結(jié)點(diǎn)有不同的處理方法筹裕。
  2. 若該結(jié)點(diǎn)為非葉結(jié)點(diǎn),且被刪關(guān)鍵字為該結(jié)點(diǎn)中第i個(gè)關(guān)鍵字key[i]窄驹,則可從指針son[i]所指的子樹中找出最小關(guān)鍵字Y朝卒,代替key[i]的位置,然后在葉結(jié)點(diǎn)中刪去Y乐埠。

因此抗斤,把在非葉結(jié)點(diǎn)刪除關(guān)鍵字k的問題就變成了刪除葉子結(jié)點(diǎn)中的關(guān)鍵字的問題了囚企。在B-樹葉結(jié)點(diǎn)上刪除一個(gè)關(guān)鍵字的方法是
首先將要?jiǎng)h除的關(guān)鍵字 k直接從該葉子結(jié)點(diǎn)中刪除。然后根據(jù)不同情況分別作相應(yīng)的處理瑞眼,共有三種可能情況:

  1. 如果被刪關(guān)鍵字所在結(jié)點(diǎn)的原關(guān)鍵字個(gè)數(shù)不貧困龙宏,說明刪去該關(guān)鍵字后該結(jié)點(diǎn)仍滿足B-樹的定義。只需從該結(jié)點(diǎn)中直接刪去關(guān)鍵字即可伤疙。
  2. 如果被刪關(guān)鍵字所在結(jié)點(diǎn)的關(guān)鍵字個(gè)數(shù)沒脫貧银酗,說明刪去該關(guān)鍵字后該結(jié)點(diǎn)將不滿足B-樹的定義,需要調(diào)整徒像。
    調(diào)整過程為:如果其左右兄弟結(jié)點(diǎn)中有“多余”的關(guān)鍵字,即與該結(jié)點(diǎn)相鄰的右(左)兄弟結(jié)點(diǎn)中的關(guān)鍵字?jǐn)?shù)目多余黍特。則可將右(左)兄弟結(jié)點(diǎn)中最小(大)關(guān)鍵字上移至雙親結(jié)點(diǎn)厨姚。而將雙親結(jié)點(diǎn)中行瞥骸(大)于該上移關(guān)鍵字的關(guān)鍵字下移至被刪關(guān)鍵字所在結(jié)點(diǎn)中,相當(dāng)于一次旋轉(zhuǎn)。
  3. 如果左右兄弟結(jié)點(diǎn)中沒有“多余”的關(guān)鍵字谬墙,即與該結(jié)點(diǎn)相鄰的右(左)兄弟結(jié)點(diǎn)中的關(guān)鍵字?jǐn)?shù)目均貧困1今布。這種情況比較復(fù)雜。需把要?jiǎng)h除關(guān)鍵字的結(jié)點(diǎn)與其左(或右)兄弟結(jié)點(diǎn)以及雙親結(jié)點(diǎn)中分割二者的關(guān)鍵字合并成一個(gè)結(jié)點(diǎn),即在刪除關(guān)鍵字后拭抬,該結(jié)點(diǎn)中剩余的關(guān)鍵字加指針部默,加上雙親結(jié)點(diǎn)中的關(guān)鍵字Ki(是雙親結(jié)點(diǎn)指向該刪除關(guān)鍵字結(jié)點(diǎn)的左(右)兄弟結(jié)點(diǎn)的指針)所指的兄弟結(jié)點(diǎn)中去。如果因此使雙親結(jié)點(diǎn)中關(guān)鍵字個(gè)數(shù)貧困了造虎,則對此雙親結(jié)點(diǎn)做同樣處理傅蹂。以致于可能直到對根結(jié)點(diǎn)做這樣的處理而使整個(gè)樹減少一層。

總之算凿,設(shè)所刪關(guān)鍵字為非終端結(jié)點(diǎn)中的Ki份蝴,則可以指針Ai所指子樹中的最小關(guān)鍵字Y代替Ki,然后在相應(yīng)結(jié)點(diǎn)中刪除Y氓轰。對任意關(guān)鍵字的刪除都可以轉(zhuǎn)化為對最下層關(guān)鍵字的刪除婚夫。

34

35

36

37

實(shí)現(xiàn):

  package com.fredal.structure;

import java.util.Stack;

public class BTree {

    private int T;//度
    private Node root;//根
    //內(nèi)部節(jié)點(diǎn)類
    public class Node {
        int n;//個(gè)數(shù)
        int key[] = new int[2*T-1];
        Node child[] = new Node[2*T];
        boolean leaf = true;
        //試匹配
        public int Find(int k){
            for (int i = 0 ; i < this.n ; i++) {
                if (this.key[i] == k) {
                    return i;
                }
            }
            return -1;
        };
    }
    
    public BTree(int _T) {
        T = _T;
        root = new Node();
        root.n = 0;
        root.leaf = true;//剛開始都是樹葉
    }
    //搜索    
    private Node Search (Node x,int key) {
        int i = 0;
        if (x == null) return x;
        for (i = 0 ; i < x.n ; i++) {
            if (key < x.key[i])
                break;
            if (key == x.key[i])//找到了1
                return x;
        }
        if (x.leaf)
            return null;//沒東西查了
        else 
            return Search(x.child[i],key);//找子節(jié)點(diǎn)去
    }
    //插入
    public void Insert (final int key) {
        Node r = root;
        if (r.n == 2*T - 1 ) {//滿了
            Node s = new Node();
            root = s;//root上升
            s.leaf = false;//變成不是葉子
            s.n = 0;
            s.child[0] = r;
            Split(s,0,r);//分裂
            _Insert(s,key);//直接插入
        } else {
            _Insert(r,key);//直接插入
        }
    }
    //分裂(合并) x-父節(jié)點(diǎn) pos-是第幾個(gè)孩子 y-要分裂的節(jié)點(diǎn)
    private void Split (Node x , int pos , Node y) {
        Node z = new Node();
        z.leaf = y.leaf;
        z.n = T - 1;//更新大小
        for (int j = 0 ; j < T - 1 ; j++) {
            z.key[j] = y.key[j+T];//賦值 對半分
        }
        if (! y.leaf) {//不是葉子節(jié)點(diǎn) 重新分配子節(jié)點(diǎn)
            for (int j = 0 ; j < T ; j++) 
                z.child[j] = y.child[j+T];//子節(jié)點(diǎn)也分對半
        }
        y.n = T-1;//更新大小
        for (int j = x.n ; j >= pos+1 ; j--)
            x.child[j+1] = x.child[j];//挪位
        x.child[pos+1] = z;//插入     
        for (int j = x.n-1 ; j >= pos ; j--)//挪位
            x.key[j+1] = x.key[j];
        x.key[pos] = y.key[T-1];//插入
        x.n = x.n + 1;
    }
    //根不滿時(shí)候插入
    final private void _Insert (Node x , int k) {   
        if (x.leaf) {//葉子節(jié)點(diǎn) 直接插入
            int i = 0;
            for (i = x.n-1 ; i >= 0 && k < x.key[i] ; i--)
                x.key[i+1] = x.key[i];
            x.key[i+1] = k;
            x.n = x.n + 1; 
        } else {
            int i = 0;
            for (i = x.n-1 ; i >= 0 && k < x.key[i] ; i--){};//找合適的孩子節(jié)點(diǎn)插
            i++;
            Node tmp = x.child[i];
            if (tmp.n == 2*T -1) {//孩子節(jié)點(diǎn)滿了
                Split(x,i,tmp);//分裂與合并
                if ( k > x.key[i])//再找一遍
                    i++;
            }
            _Insert(x.child[i], k);//插入子節(jié)點(diǎn)
        }
        
    }
    
    public void Show () {
        Show(root);
    }
    //打印
    private void Show (Node x) {
        assert(x == null);
        System.out.print(x.leaf +":" );
        for ( int i = 0 ; i < x.n ; i++) {
            System.out.print(x.key[i]+ " ");
        }
        System.out.println();
        if (!x.leaf) {
            for (int i = 0 ;i <  x.n + 1; i++) {
                Show(x.child[i]);//輸出子節(jié)點(diǎn)
            }
        }
    }
    
    public void Remove (int key) {
        Node x = Search(root, key);
        if (x == null) {
            return;
        }
        Remove(root,key);
    }
    //刪除    
    private void Remove (Node x , int key) {
        int pos = x.Find(key);//先試匹配
        if (pos != -1) {//找到了 說明當(dāng)前就包含要?jiǎng)h除的
            if (x.leaf) {//刪除的是葉子節(jié)點(diǎn)
                int i = 0 ;
                for (i = 0 ; i < x.n && x.key[i] != key ; i++){};//找到刪除位置
                for ( ; i < x.n ; i++) {
                    if (i != 2*T - 2){
                        x.key[i] = x.key[i+1];
                    }
                }
                x.n--;
                return;
            }
            if (!x.leaf){//刪除的不是葉子節(jié)點(diǎn)
                    Node pred = x.child[pos];//當(dāng)前節(jié)點(diǎn)元素的右節(jié)點(diǎn)
                    int predKey = 0;
                    if (pred.n >= T) {//不貧困
                        for (;;) {
                            if (pred.leaf) {//是葉子
                                predKey = pred.key[pred.n - 1];
                                break;
                            } else {//繼續(xù)往下 循環(huán)直到葉子
                                pred = pred.child[pred.n];
                            }
                        }
                        Remove (pred, predKey);//葉子 直接刪除
                        x.key[pos] = predKey;//移到父節(jié)點(diǎn)去
                        return;
                    }
                    //自己貧困了 看看兄弟節(jié)點(diǎn)
                    Node nextNode = x.child[pos+1];
                    if (nextNode.n >= T) {//兄弟節(jié)點(diǎn)不貧困
                        int nextKey = nextNode.key[0];
                        if (!nextNode.leaf){//不是葉子
                            nextNode = nextNode.child[0];
                            for (;;) {
                                if (nextNode.leaf) {//是葉子
                                    nextKey = nextNode.key[nextNode.n-1];
                                    break;
                                } else {
                                    nextNode = nextNode.child[nextNode.n];//往下降直到葉子
                                }
                            }
                        }
                        Remove(nextNode, nextKey);//刪除
                        x.key[pos] = nextKey;//移到父節(jié)點(diǎn)
                        return;
                    }
                    //都貧困
                    int temp = pred.n + 1;
                    pred.key[pred.n++] = x.key[pos];//先從父節(jié)點(diǎn)借一個(gè)
                    for (int i = 0, j = pred.n ; i < nextNode.n ; i++) {//和兄弟合并
                        pred.key[j++] = nextNode.key[i];
                        pred.n++;
                    }
                    for (int i = 0 ; i < nextNode.n+1 ; i++){//子節(jié)點(diǎn)也合并
                        pred.child[temp++] = nextNode.child[i];
                    }               
                    x.child[pos] = pred;//更新
                    for (int i = pos ; i < x.n ; i++) {//父節(jié)點(diǎn)關(guān)鍵字更新
                        if (i != 2*T - 2) {
                            x.key[i] = x.key[i+1];
                        }
                    }
                    for (int i = pos+1 ; i < x.n+1 ; i++) {//父節(jié)點(diǎn)孩子節(jié)點(diǎn)更新
                        if (i != 2*T - 1) {
                            x.child[i] = x.child[i+1];
                        }
                    }
                    x.n--;//個(gè)數(shù)遞減
                    if (x.n == 0) {
                        if (x == root) {
                            root = x.child[0];
                        }
                        x = x.child[0];
                    }
                    Remove(pred,key);//刪除
                    return;
            }
        } else {//沒找到 當(dāng)前不包含刪除的
            for (pos = 0 ; pos < x.n ; pos++) {
                if (x.key[pos] > key)//尋找包含目標(biāo)的孩子節(jié)點(diǎn)
                    break;
            }
            Node tmp = x.child[pos];//找到的這個(gè)節(jié)點(diǎn)
            if (tmp.n >= T) {//不貧困 就刪除(不一定就找到了,可能只是包含)
                Remove (tmp,key);
                return;
            }
            if (true) {
                Node nb = null;
                int devider = -1;
                if (pos != x.n && x.child[pos+1].n >= T) {//右孩子不貧困
                    devider = x.key[pos];
                    nb = x.child[pos+1];
                    x.key[pos] = nb.key[0];//父節(jié)點(diǎn)借用孩子節(jié)點(diǎn)
                    tmp.key[tmp.n++] = devider;//借用父節(jié)點(diǎn),相當(dāng)于一次旋轉(zhuǎn)
                    tmp.child[tmp.n] = nb.child[0];//借用兄弟節(jié)點(diǎn)
                    for (int i = 1 ; i < nb.n ; i++) {//重新挪位置
                        nb.key[i-1] = nb.key[i];
                    }
                    for (int i = 1 ; i <= nb.n ; i++) {
                        nb.child[i-1] = nb.child[i];
                    }
                    nb.n--;
                    Remove(tmp,key);//刪除
                    return;
                } else if (pos != 0 && x.child[pos-1].n >= T){//左孩子不貧困,和上面類似
                    devider = x.key[pos-1];
                    nb = x.child[pos-1];
                    x.key[pos-1] = nb.key[nb.n-1];
                    Node child = nb.child[nb.n];
                    nb.n--;
                    for(int i = tmp.n ; i > 0 ; i--) {
                        tmp.key[i] = tmp.key[i-1];
                    }
                    tmp.key[0] = devider;
                    for(int i = tmp.n + 1 ; i > 0 ; i--) {
                        tmp.child[i] = tmp.child[i-1];
                    }
                    tmp.child[0] = child;
                    tmp.n++;
                    Remove(tmp,key);
                    return;
                } else {//都貧困 合并
                    Node lt = null;//左孩子
                    Node rt = null;//右孩子
                    boolean last = false;
                    if (pos != x.n) {
                        devider = x.key[pos];
                        lt = x.child[pos]; 
                        rt = x.child[pos+1];
                    } else {
                        devider = x.key[pos-1];
                        rt = x.child[pos];
                        lt = x.child[pos-1];
                        last = true;
                        pos--;
                    }
                    for (int i = pos; i < x.n-1  ; i++){
                        x.key[i] = x.key[i+1];
                    }

                    for(int i = pos+1 ; i < x.n ; i++) {
                        x.child[i] = x.child[i+1];
                    }
                    x.n--;
                    lt.key[lt.n++] = devider;
                    for (int i = 0, j = lt.n; i < rt.n+1 ; i++,j++) {
                        if (i < rt.n) {
                            lt.key[j] = rt.key[i];
                        }
                        lt.child[j] = rt.child[i];
                    }
                    lt.n += rt.n;
                    if (x.n == 0) {
                        if (x == root) {
                            root = x.child[0];
                        }
                        x = x.child[0];
                    }
                    Remove(lt,key);                 
                    return;
                }               
            }
        }
    }

    public void Task (int a, int b){
        Stack<Integer> st = new Stack<Integer>();
        FindKeys(a,b,root,st);
        while (st.isEmpty() == false) {
            this.Remove(root,st.pop());
        }
    }
    
    private void FindKeys (int a, int b, Node x, Stack<Integer> st){
        int i = 0;
        for (i = 0 ; i < x.n && x.key[i] < b; i++) {
            if ( x.key[i] > a  ) {
                st.push(x.key[i]);
            }
        }
        if (!x.leaf) {
            for (int j = 0 ; j < i+1 ; j++) {
                FindKeys(a,b,x.child[j],st);
            }
        }
    }
    
    public boolean Contain(int k) {
        if (this.Search(root, k) != null) {
            return true;
        } else {
            return false;
        }
    }
    
    public static void main(String[] args) {
        BTree t=new BTree(3);
        t.Insert(3);
        t.Insert(14);
        t.Insert(7);
        t.Insert(1);
        t.Insert(8);
        t.Insert(5);
        t.Insert(11);
        t.Insert(17);
        t.Insert(13);
        t.Insert(6);
        t.Insert(23);
        t.Insert(12);
        t.Insert(20);
        t.Insert(9);
        t.Insert(87);
        t.Insert(34);
        t.Show();
        System.out.println("-----");
        t.Remove(7);
        t.Remove(6);
        t.Remove(1);
        t.Remove(11);
        t.Show();
    }
    
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市署鸡,隨后出現(xiàn)的幾起案子案糙,更是在濱河造成了極大的恐慌,老刑警劉巖靴庆,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件时捌,死亡現(xiàn)場離奇詭異,居然都是意外死亡炉抒,警方通過查閱死者的電腦和手機(jī)奢讨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來焰薄,“玉大人拿诸,你說我怎么就攤上這事入录。” “怎么了佳镜?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長凡桥。 經(jīng)常有香客問我蟀伸,道長,這世上最難降的妖魔是什么缅刽? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任啊掏,我火速辦了婚禮,結(jié)果婚禮上衰猛,老公的妹妹穿的比我還像新娘迟蜜。我一直安慰自己,他們只是感情好啡省,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布娜睛。 她就那樣靜靜地躺著,像睡著了一般卦睹。 火紅的嫁衣襯著肌膚如雪畦戒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天结序,我揣著相機(jī)與錄音障斋,去河邊找鬼。 笑死徐鹤,一個(gè)胖子當(dāng)著我的面吹牛垃环,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播返敬,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼遂庄,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了救赐?” 一聲冷哼從身側(cè)響起涧团,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎经磅,沒想到半個(gè)月后泌绣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡预厌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年阿迈,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片轧叽。...
    茶點(diǎn)故事閱讀 39,977評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡苗沧,死狀恐怖刊棕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情待逞,我是刑警寧澤甥角,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站识樱,受9級特大地震影響嗤无,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜怜庸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一当犯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧割疾,春花似錦嚎卫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至担扑,卻和暖如春恰响,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背涌献。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工胚宦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人燕垃。 一個(gè)月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓枢劝,卻偏偏與公主長得像,于是被迫代替她去往敵國和親卜壕。 傳聞我的和親對象是個(gè)殘疾皇子您旁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容

  • B樹的定義 一棵m階的B樹滿足下列條件: 樹中每個(gè)結(jié)點(diǎn)至多有m個(gè)孩子。 除根結(jié)點(diǎn)和葉子結(jié)點(diǎn)外轴捎,其它每個(gè)結(jié)點(diǎn)至少有m...
    文檔隨手記閱讀 13,221評論 0 25
  • 一. 算法之變鹤盒,結(jié)構(gòu)為宗 計(jì)算機(jī)在很多情況下被應(yīng)用于檢索數(shù)據(jù),比如航空和鐵路運(yùn)輸業(yè)的航班信息和列車時(shí)刻表的查詢侦副,都...
    Leesper閱讀 6,906評論 13 42
  • 樹的概述 樹是一種非常常用的數(shù)據(jù)結(jié)構(gòu)侦锯,樹與前面介紹的線性表,棧秦驯,隊(duì)列等線性結(jié)構(gòu)不同尺碰,樹是一種非線性結(jié)構(gòu) 1.樹的定...
    Jack921閱讀 4,455評論 1 31
  • 原文鏈接 B樹 1.前言: 動(dòng)態(tài)查找樹主要有:二叉查找樹(Binary Search Tree),平衡二叉查找樹(...
    非典型程序員閱讀 1,159評論 0 3
  • 內(nèi)容整理于魚c工作室教程 1. 樹的基本概念 1.1 樹的定義 樹(Tree)是n(n>=0)個(gè)結(jié)點(diǎn)的有限集。 當(dāng)...
    阿阿阿阿毛閱讀 1,082評論 0 1