• 伸展树(三)之 Java的实现


    概要

    前面分别通过C和C++实现了伸展树,本章给出伸展树的Java版本。基本算法和原理都与前两章一样。
    1. 伸展树的介绍
    2. 伸展树的Java实现(完整源码)
    3. 伸展树的Java测试程序

    转载请注明出处:http://www.cnblogs.com/skywang12345/p/3604286.html


    更多内容数据结构与算法系列 目录 

    (01) 伸展树(一)之 图文解析 和 C语言的实现
    (02) 伸展树(二)之 C++的实现
    (03) 伸展树(三)之 Java的实现

    伸展树的介绍

    伸展树(Splay Tree)是特殊的二叉查找树。
    它的特殊是指,它除了本身是棵二叉查找树之外,它还具备一个特点: 当某个节点被访问时,伸展树会通过旋转使该节点成为树根。这样做的好处是,下次要访问该节点时,能够迅速的访问到该节点。

    伸展树的Java实现

    1. 基本定义

    public class SplayTree<T extends Comparable<T>> {
    
        private SplayTreeNode<T> mRoot;    // 根结点
    
        public class SplayTreeNode<T extends Comparable<T>> {
            T key;                // 关键字(键值)
            SplayTreeNode<T> left;    // 左孩子
            SplayTreeNode<T> right;    // 右孩子
    
            public SplayTreeNode() {
                this.left = null;
                this.right = null;
            }
    
            public SplayTreeNode(T key, SplayTreeNode<T> left, SplayTreeNode<T> right) {
                this.key = key;
                this.left = left;
                this.right = right;
            }
        }
    
            ...
    }

    SplayTree是伸展树,而SplayTreeNode是伸展树节点。在此,我将SplayTreeNode定义为SplayTree的内部类。在伸展树SplayTree中包含了伸展树的根节点mRoot。SplayTreeNode包括的几个组成元素:
    (01) key -- 是关键字,是用来对伸展树的节点进行排序的。
    (02) left -- 是左孩子。
    (03) right -- 是右孩子。

    2. 旋转

    旋转是伸展树中需要重点关注的,它的代码如下:

    /* 
     * 旋转key对应的节点为根节点,并返回根节点。
     *
     * 注意:
     *   (a):伸展树中存在"键值为key的节点"。
     *          将"键值为key的节点"旋转为根节点。
     *   (b):伸展树中不存在"键值为key的节点",并且key < tree.key。
     *      b-1 "键值为key的节点"的前驱节点存在的话,将"键值为key的节点"的前驱节点旋转为根节点。
     *      b-2 "键值为key的节点"的前驱节点存在的话,则意味着,key比树中任何键值都小,那么此时,将最小节点旋转为根节点。
     *   (c):伸展树中不存在"键值为key的节点",并且key > tree.key。
     *      c-1 "键值为key的节点"的后继节点存在的话,将"键值为key的节点"的后继节点旋转为根节点。
     *      c-2 "键值为key的节点"的后继节点不存在的话,则意味着,key比树中任何键值都大,那么此时,将最大节点旋转为根节点。
     */
    private SplayTreeNode<T> splay(SplayTreeNode<T> tree, T key) {
        if (tree == null) 
            return tree;
    
        SplayTreeNode<T> N = new SplayTreeNode<T>();
        SplayTreeNode<T> l = N;
        SplayTreeNode<T> r = N;
        SplayTreeNode<T> c;
    
        for (;;) {
    
            int cmp = key.compareTo(tree.key);
            if (cmp < 0) {
    
                if (tree.left == null)
                    break;
    
                if (key.compareTo(tree.left.key) < 0) {
                    c = tree.left;                           /* rotate right */
                    tree.left = c.right;
                    c.right = tree;
                    tree = c;
                    if (tree.left == null) 
                        break;
                }
                r.left = tree;                               /* link right */
                r = tree;
                tree = tree.left;
            } else if (cmp > 0) {
    
                if (tree.right == null) 
                    break;
    
                if (key.compareTo(tree.right.key) > 0) {
                    c = tree.right;                          /* rotate left */
                    tree.right = c.left;
                    c.left = tree;
                    tree = c;
                    if (tree.right == null) 
                        break;
                }
    
                l.right = tree;                              /* link left */
                l = tree;
                tree = tree.right;
            } else {
                break;
            }
        }
    
        l.right = tree.left;                                /* assemble */
        r.left = tree.right;
        tree.left = N.right;
        tree.right = N.left;
    
        return tree;
    }
    
    public void splay(T key) {
        mRoot = splay(mRoot, key);
    }

    上面的代码的作用:将"键值为key的节点"旋转为根节点,并返回根节点。它的处理情况共包括:
    (a):伸展树中存在"键值为key的节点"。
            将"键值为key的节点"旋转为根节点。
    (b):伸展树中不存在"键值为key的节点",并且key < tree->key。
            b-1) "键值为key的节点"的前驱节点存在的话,将"键值为key的节点"的前驱节点旋转为根节点。
            b-2) "键值为key的节点"的前驱节点存在的话,则意味着,key比树中任何键值都小,那么此时,将最小节点旋转为根节点。
    (c):伸展树中不存在"键值为key的节点",并且key > tree->key。
            c-1) "键值为key的节点"的后继节点存在的话,将"键值为key的节点"的后继节点旋转为根节点。
            c-2) "键值为key的节点"的后继节点不存在的话,则意味着,key比树中任何键值都大,那么此时,将最大节点旋转为根节点。

    下面列举个例子分别对a进行说明。

    在下面的伸展树中查找10,,共包括"右旋" --> "右链接" --> "组合"这3步。


    01, 右旋
    对应代码中的"rotate right"部分


    02, 右链接
    对应代码中的"link right"部分


    03. 组合
    对应代码中的"assemble"部分


    提示:如果在上面的伸展树中查找"70",则正好与"示例1"对称,而对应的操作则分别是"rotate left", "link left"和"assemble"。
    其它的情况,例如"查找15是b-1的情况,查找5是b-2的情况"等等,这些都比较简单,大家可以自己分析。


    3. 插入

    插入代码

    /* 
    * 将结点插入到伸展树中,并返回根节点
     *
     * 参数说明:
     *     tree 伸展树的
     *     z 插入的结点
     */
    private SplayTreeNode<T> insert(SplayTreeNode<T> tree, SplayTreeNode<T> z) {
        int cmp;
        SplayTreeNode<T> y = null;
        SplayTreeNode<T> x = tree;
    
        // 查找z的插入位置
        while (x != null) {
            y = x;
            cmp = z.key.compareTo(x.key);
            if (cmp < 0)
                x = x.left;
            else if (cmp > 0)
                x = x.right;
            else {
                System.out.printf("不允许插入相同节点(%d)!
    ", z.key);
                z=null;
                return tree;
            }
        }
    
        if (y==null)
            tree = z;
        else {
            cmp = z.key.compareTo(y.key);
            if (cmp < 0)
                y.left = z;
            else
                y.right = z;
        }
    
        return tree;
    }
    
    public void insert(T key) {
        SplayTreeNode<T> z=new SplayTreeNode<T>(key,null,null);
    
        // 如果新建结点失败,则返回。
        if ((z=new SplayTreeNode<T>(key,null,null)) == null)
            return ;
    
        // 插入节点
        mRoot = insert(mRoot, z);
        // 将节点(key)旋转为根节点
        mRoot = splay(mRoot, key);
    }

    insert(key)是提供给外部的接口,它的作用是新建节点(节点的键值为key),并将节点插入到伸展树中;然后,将该节点旋转为根节点。
    insert(tree, z)是内部接口,它的作用是将节点z插入到tree中。insert(tree, z)在将z插入到tree中时,仅仅只将tree当作是一棵二叉查找树,而且不允许插入相同节点。

    4. 删除
    删除代码

    /* 
     * 删除结点(z),并返回被删除的结点
     *
     * 参数说明:
     *     bst 伸展树
     *     z 删除的结点
     */
    private SplayTreeNode<T> remove(SplayTreeNode<T> tree, T key) {
        SplayTreeNode<T> x;
    
        if (tree == null) 
            return null;
    
        // 查找键值为key的节点,找不到的话直接返回。
        if (search(tree, key) == null)
            return tree;
    
        // 将key对应的节点旋转为根节点。
        tree = splay(tree, key);
    
        if (tree.left != null) {
            // 将"tree的前驱节点"旋转为根节点
            x = splay(tree.left, key);
            // 移除tree节点
            x.right = tree.right;
        }
        else
            x = tree.right;
    
        tree = null;
    
        return x;
    }
    
    public void remove(T key) {
        mRoot = remove(mRoot, key);
    }

    remove(key)是外部接口,remove(tree, key)是内部接口。
    remove(tree, key)的作用是:删除伸展树中键值为key的节点。
    它会先在伸展树中查找键值为key的节点。若没有找到的话,则直接返回。若找到的话,则将该节点旋转为根节点,然后再删除该节点。


    关于"前序遍历"、"中序遍历"、"后序遍历"、"最大值"、"最小值"、"查找"、"打印伸展树"、"销毁伸展树"等接口就不再单独介绍了,Please RTFSC(Read The Fucking Source Code)!这些接口,与前面介绍的"二叉查找树"、"AVL树"的相关接口都是类似的。

     

    伸展树的Java实现(完整源码)

    伸展树的实现文件(SplayTree.java)

      1 /**
      2  * Java 语言: 伸展树
      3  *
      4  * @author skywang
      5  * @date 2014/02/03
      6  */
      7 
      8 public class SplayTree<T extends Comparable<T>> {
      9 
     10     private SplayTreeNode<T> mRoot;    // 根结点
     11 
     12     public class SplayTreeNode<T extends Comparable<T>> {
     13         T key;                // 关键字(键值)
     14         SplayTreeNode<T> left;    // 左孩子
     15         SplayTreeNode<T> right;    // 右孩子
     16 
     17         public SplayTreeNode() {
     18             this.left = null;
     19             this.right = null;
     20         }
     21 
     22         public SplayTreeNode(T key, SplayTreeNode<T> left, SplayTreeNode<T> right) {
     23             this.key = key;
     24             this.left = left;
     25             this.right = right;
     26         }
     27     }
     28 
     29     public SplayTree() {
     30         mRoot=null;
     31     }
     32 
     33     /*
     34      * 前序遍历"伸展树"
     35      */
     36     private void preOrder(SplayTreeNode<T> tree) {
     37         if(tree != null) {
     38             System.out.print(tree.key+" ");
     39             preOrder(tree.left);
     40             preOrder(tree.right);
     41         }
     42     }
     43 
     44     public void preOrder() {
     45         preOrder(mRoot);
     46     }
     47 
     48     /*
     49      * 中序遍历"伸展树"
     50      */
     51     private void inOrder(SplayTreeNode<T> tree) {
     52         if(tree != null) {
     53             inOrder(tree.left);
     54             System.out.print(tree.key+" ");
     55             inOrder(tree.right);
     56         }
     57     }
     58 
     59     public void inOrder() {
     60         inOrder(mRoot);
     61     }
     62 
     63 
     64     /*
     65      * 后序遍历"伸展树"
     66      */
     67     private void postOrder(SplayTreeNode<T> tree) {
     68         if(tree != null)
     69         {
     70             postOrder(tree.left);
     71             postOrder(tree.right);
     72             System.out.print(tree.key+" ");
     73         }
     74     }
     75 
     76     public void postOrder() {
     77         postOrder(mRoot);
     78     }
     79 
     80 
     81     /*
     82      * (递归实现)查找"伸展树x"中键值为key的节点
     83      */
     84     private SplayTreeNode<T> search(SplayTreeNode<T> x, T key) {
     85         if (x==null)
     86             return x;
     87 
     88         int cmp = key.compareTo(x.key);
     89         if (cmp < 0)
     90             return search(x.left, key);
     91         else if (cmp > 0)
     92             return search(x.right, key);
     93         else
     94             return x;
     95     }
     96 
     97     public SplayTreeNode<T> search(T key) {
     98         return search(mRoot, key);
     99     }
    100 
    101     /*
    102      * (非递归实现)查找"伸展树x"中键值为key的节点
    103      */
    104     private SplayTreeNode<T> iterativeSearch(SplayTreeNode<T> x, T key) {
    105         while (x!=null) {
    106             int cmp = key.compareTo(x.key);
    107 
    108             if (cmp < 0) 
    109                 x = x.left;
    110             else if (cmp > 0) 
    111                 x = x.right;
    112             else
    113                 return x;
    114         }
    115 
    116         return x;
    117     }
    118 
    119     public SplayTreeNode<T> iterativeSearch(T key) {
    120         return iterativeSearch(mRoot, key);
    121     }
    122 
    123     /* 
    124      * 查找最小结点:返回tree为根结点的伸展树的最小结点。
    125      */
    126     private SplayTreeNode<T> minimum(SplayTreeNode<T> tree) {
    127         if (tree == null)
    128             return null;
    129 
    130         while(tree.left != null)
    131             tree = tree.left;
    132         return tree;
    133     }
    134 
    135     public T minimum() {
    136         SplayTreeNode<T> p = minimum(mRoot);
    137         if (p != null)
    138             return p.key;
    139 
    140         return null;
    141     }
    142      
    143     /* 
    144      * 查找最大结点:返回tree为根结点的伸展树的最大结点。
    145      */
    146     private SplayTreeNode<T> maximum(SplayTreeNode<T> tree) {
    147         if (tree == null)
    148             return null;
    149 
    150         while(tree.right != null)
    151             tree = tree.right;
    152         return tree;
    153     }
    154 
    155     public T maximum() {
    156         SplayTreeNode<T> p = maximum(mRoot);
    157         if (p != null)
    158             return p.key;
    159 
    160         return null;
    161     }
    162 
    163     /* 
    164      * 旋转key对应的节点为根节点,并返回根节点。
    165      *
    166      * 注意:
    167      *   (a):伸展树中存在"键值为key的节点"。
    168      *          将"键值为key的节点"旋转为根节点。
    169      *   (b):伸展树中不存在"键值为key的节点",并且key < tree.key。
    170      *      b-1 "键值为key的节点"的前驱节点存在的话,将"键值为key的节点"的前驱节点旋转为根节点。
    171      *      b-2 "键值为key的节点"的前驱节点存在的话,则意味着,key比树中任何键值都小,那么此时,将最小节点旋转为根节点。
    172      *   (c):伸展树中不存在"键值为key的节点",并且key > tree.key。
    173      *      c-1 "键值为key的节点"的后继节点存在的话,将"键值为key的节点"的后继节点旋转为根节点。
    174      *      c-2 "键值为key的节点"的后继节点不存在的话,则意味着,key比树中任何键值都大,那么此时,将最大节点旋转为根节点。
    175      */
    176     private SplayTreeNode<T> splay(SplayTreeNode<T> tree, T key) {
    177         if (tree == null) 
    178             return tree;
    179 
    180         SplayTreeNode<T> N = new SplayTreeNode<T>();
    181         SplayTreeNode<T> l = N;
    182         SplayTreeNode<T> r = N;
    183         SplayTreeNode<T> c;
    184 
    185         for (;;) {
    186 
    187             int cmp = key.compareTo(tree.key);
    188             if (cmp < 0) {
    189 
    190                 if (tree.left == null)
    191                     break;
    192 
    193                 if (key.compareTo(tree.left.key) < 0) {
    194                     c = tree.left;                           /* rotate right */
    195                     tree.left = c.right;
    196                     c.right = tree;
    197                     tree = c;
    198                     if (tree.left == null) 
    199                         break;
    200                 }
    201                 r.left = tree;                               /* link right */
    202                 r = tree;
    203                 tree = tree.left;
    204             } else if (cmp > 0) {
    205 
    206                 if (tree.right == null) 
    207                     break;
    208 
    209                 if (key.compareTo(tree.right.key) > 0) {
    210                     c = tree.right;                          /* rotate left */
    211                     tree.right = c.left;
    212                     c.left = tree;
    213                     tree = c;
    214                     if (tree.right == null) 
    215                         break;
    216                 }
    217 
    218                 l.right = tree;                              /* link left */
    219                 l = tree;
    220                 tree = tree.right;
    221             } else {
    222                 break;
    223             }
    224         }
    225 
    226         l.right = tree.left;                                /* assemble */
    227         r.left = tree.right;
    228         tree.left = N.right;
    229         tree.right = N.left;
    230 
    231         return tree;
    232     }
    233 
    234     public void splay(T key) {
    235         mRoot = splay(mRoot, key);
    236     }
    237 
    238     /* 
    239      * 将结点插入到伸展树中,并返回根节点
    240      *
    241      * 参数说明:
    242      *     tree 伸展树的
    243      *     z 插入的结点
    244      */
    245     private SplayTreeNode<T> insert(SplayTreeNode<T> tree, SplayTreeNode<T> z) {
    246         int cmp;
    247         SplayTreeNode<T> y = null;
    248         SplayTreeNode<T> x = tree;
    249 
    250         // 查找z的插入位置
    251         while (x != null) {
    252             y = x;
    253             cmp = z.key.compareTo(x.key);
    254             if (cmp < 0)
    255                 x = x.left;
    256             else if (cmp > 0)
    257                 x = x.right;
    258             else {
    259                 System.out.printf("不允许插入相同节点(%d)!
    ", z.key);
    260                 z=null;
    261                 return tree;
    262             }
    263         }
    264 
    265         if (y==null)
    266             tree = z;
    267         else {
    268             cmp = z.key.compareTo(y.key);
    269             if (cmp < 0)
    270                 y.left = z;
    271             else
    272                 y.right = z;
    273         }
    274 
    275         return tree;
    276     }
    277 
    278     public void insert(T key) {
    279         SplayTreeNode<T> z=new SplayTreeNode<T>(key,null,null);
    280 
    281         // 如果新建结点失败,则返回。
    282         if ((z=new SplayTreeNode<T>(key,null,null)) == null)
    283             return ;
    284 
    285         // 插入节点
    286         mRoot = insert(mRoot, z);
    287         // 将节点(key)旋转为根节点
    288         mRoot = splay(mRoot, key);
    289     }
    290 
    291     /* 
    292      * 删除结点(z),并返回被删除的结点
    293      *
    294      * 参数说明:
    295      *     bst 伸展树
    296      *     z 删除的结点
    297      */
    298     private SplayTreeNode<T> remove(SplayTreeNode<T> tree, T key) {
    299         SplayTreeNode<T> x;
    300 
    301         if (tree == null) 
    302             return null;
    303 
    304         // 查找键值为key的节点,找不到的话直接返回。
    305         if (search(tree, key) == null)
    306             return tree;
    307 
    308         // 将key对应的节点旋转为根节点。
    309         tree = splay(tree, key);
    310 
    311         if (tree.left != null) {
    312             // 将"tree的前驱节点"旋转为根节点
    313             x = splay(tree.left, key);
    314             // 移除tree节点
    315             x.right = tree.right;
    316         }
    317         else
    318             x = tree.right;
    319 
    320         tree = null;
    321 
    322         return x;
    323     }
    324 
    325     public void remove(T key) {
    326         mRoot = remove(mRoot, key);
    327     }
    328 
    329     /*
    330      * 销毁伸展树
    331      */
    332     private void destroy(SplayTreeNode<T> tree) {
    333         if (tree==null)
    334             return ;
    335 
    336         if (tree.left != null)
    337             destroy(tree.left);
    338         if (tree.right != null)
    339             destroy(tree.right);
    340 
    341         tree=null;
    342     }
    343 
    344     public void clear() {
    345         destroy(mRoot);
    346         mRoot = null;
    347     }
    348 
    349     /*
    350      * 打印"伸展树"
    351      *
    352      * key        -- 节点的键值 
    353      * direction  --  0,表示该节点是根节点;
    354      *               -1,表示该节点是它的父结点的左孩子;
    355      *                1,表示该节点是它的父结点的右孩子。
    356      */
    357     private void print(SplayTreeNode<T> tree, T key, int direction) {
    358 
    359         if(tree != null) {
    360 
    361             if(direction==0)    // tree是根节点
    362                 System.out.printf("%2d is root
    ", tree.key);
    363             else                // tree是分支节点
    364                 System.out.printf("%2d is %2d's %6s child
    ", tree.key, key, direction==1?"right" : "left");
    365 
    366             print(tree.left, tree.key, -1);
    367             print(tree.right,tree.key,  1);
    368         }
    369     }
    370 
    371     public void print() {
    372         if (mRoot != null)
    373             print(mRoot, mRoot.key, 0);
    374     }
    375 }
    View Code

    伸展树的测试程序(SplayTreeTest.java)

     1 /**
     2  * Java 语言: 伸展树
     3  *
     4  * @author skywang
     5  * @date 2014/02/03
     6  */
     7 public class SplayTreeTest {
     8 
     9     private static final int arr[] = {10,50,40,30,20,60};
    10 
    11     public static void main(String[] args) {
    12         int i, ilen;
    13         SplayTree<Integer> tree=new SplayTree<Integer>();
    14 
    15         System.out.print("== 依次添加: ");
    16         ilen = arr.length;
    17         for(i=0; i<ilen; i++) {
    18             System.out.print(arr[i]+" ");
    19             tree.insert(arr[i]);
    20         }
    21 
    22         System.out.print("
    == 前序遍历: ");
    23         tree.preOrder();
    24 
    25         System.out.print("
    == 中序遍历: ");
    26         tree.inOrder();
    27 
    28         System.out.print("
    == 后序遍历: ");
    29         tree.postOrder();
    30         System.out.println();
    31 
    32         System.out.println("== 最小值: "+ tree.minimum());
    33         System.out.println("== 最大值: "+ tree.maximum());
    34         System.out.println("== 树的详细信息: ");
    35         tree.print();
    36 
    37         i = 30;
    38         System.out.printf("
    == 旋转节点(%d)为根节点
    ", i);
    39         tree.splay(i);
    40         System.out.printf("== 树的详细信息: 
    ");
    41         tree.print();
    42 
    43         // 销毁二叉树
    44         tree.clear();
    45     }
    46 }
    View Code


    在二叉查找树的Java实现中,使用了泛型,也就意味着它支持任意类型;但是该类型必须要实现Comparable接口。

     

    伸展树的Java测试程序

    伸展树的测试程序运行结果如下:

    == 依次添加: 10 50 40 30 20 60 
    == 前序遍历: 60 30 20 10 50 40 
    == 中序遍历: 10 20 30 40 50 60 
    == 后序遍历: 10 20 40 50 30 60 
    == 最小值: 10
    == 最大值: 60
    == 树的详细信息: 
    60 is root
    30 is 60's   left child
    20 is 30's   left child
    10 is 20's   left child
    50 is 30's  right child
    40 is 50's   left child
    
    == 旋转节点(30)为根节点
    == 树的详细信息: 
    30 is root
    20 is 30's   left child
    10 is 20's   left child
    60 is 30's  right child
    50 is 60's   left child
    40 is 50's   left child

    测试程序的主要流程是:新建伸展树,然后向伸展树中依次插入10,50,40,30,20,60。插入完毕这些数据之后,伸展树的节点是60;此时,再旋转节点,使得30成为根节点。
    依次插入10,50,40,30,20,60示意图如下:

    将30旋转为根节点的示意图如下:


  • 相关阅读:
    使用repeater tableb绑定数据库
    运用js脚本实现table自动添加、删除行
    asp.net ListBox单选、全选、清除等功能
    .net 使用webservice 技术的测试案例
    使用.Net三层架构实现Gridview增、删、改功能
    使用用户控件AspNetPager+Gridview实现分页功能
    silverlight+wcf+linq to sql访问数据
    javascript实现乘法表(本人是菜鸟)
    oracle创建主键自增字段
    C#作Windows服务获取运行目录的方法
  • 原文地址:https://www.cnblogs.com/skywang12345/p/3604286.html
Copyright © 2020-2023  润新知