伸展樹(三)之 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)!\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旋轉為根節點的示意圖如下:


 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM