伸展树(三)之 Java的实现

落花浮王杯 提交于 2020-03-12 04:45:38

 

概要

前面分别通过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)!\n", 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)!\n", 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\n", tree.key);
363             else                // tree是分支节点
364                 System.out.printf("%2d is %2d's %6s child\n", 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("\n== 前序遍历: ");
23         tree.preOrder();
24 
25         System.out.print("\n== 中序遍历: ");
26         tree.inOrder();
27 
28         System.out.print("\n== 后序遍历: ");
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("\n== 旋转节点(%d)为根节点\n", i);
39         tree.splay(i);
40         System.out.printf("== 树的详细信息: \n");
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旋转为根节点的示意图如下:


 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!