数据结构——伸展树

转自:http://www.cnblogs.com/skywang12345/p/3604286.html

伸展树的介绍

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

伸展树的实现

1. 基本定义

 

    /// <summary>
    /// 伸展树
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class SplayTree<T> where T : IComparable<T>
    {
        private SplayTreeNode<T> mRoot;    // 根结点
        public class SplayTreeNode<T> where T : IComparable<T>
        {
            public T key;                // 关键字(键值)
            public SplayTreeNode<T> left;    // 左孩子
            public 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
                {
                    Console.WriteLine("不允许插入相同节点(%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树"的相关接口都是类似的。

 

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

伸展树的实现文件

 

    /// <summary>
    /// 伸展树
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class SplayTree<T> where T : IComparable<T>
    {
        private SplayTreeNode<T> mRoot;    // 根结点
        public class SplayTreeNode<T> where T : IComparable<T>
        {
            public T key;                // 关键字(键值)
            public SplayTreeNode<T> left;    // 左孩子
            public 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;
            }
        }

        public SplayTree()
        {
            mRoot = null;
        }

        /*
         * 前序遍历"伸展树"
         */
        private void preOrder(SplayTreeNode<T> tree)
        {
            if (tree != null)
            {
                Console.Write(tree.key + " ");
                preOrder(tree.left);
                preOrder(tree.right);
            }
        }

        public void preOrder()
        {
            preOrder(mRoot);
        }

        /*
         * 中序遍历"伸展树"
         */
        private void inOrder(SplayTreeNode<T> tree)
        {
            if (tree != null)
            {
                inOrder(tree.left);
                Console.Write(tree.key + " ");
                inOrder(tree.right);
            }
        }

        public void inOrder()
        {
            inOrder(mRoot);
        }


        /*
         * 后序遍历"伸展树"
         */
        private void postOrder(SplayTreeNode<T> tree)
        {
            if (tree != null)
            {
                postOrder(tree.left);
                postOrder(tree.right);
                Console.Write(tree.key + " ");
            }
        }

        public void postOrder()
        {
            postOrder(mRoot);
        }


        /*
         * (递归实现)查找"伸展树x"中键值为key的节点
         */
        private SplayTreeNode<T> search(SplayTreeNode<T> x, T key)
        {
            if (x == null)
                return x;

            int cmp = key.CompareTo(x.key);
            if (cmp < 0)
                return search(x.left, key);
            else if (cmp > 0)
                return search(x.right, key);
            else
                return x;
        }

        public SplayTreeNode<T> search(T key)
        {
            return search(mRoot, key);
        }

        /*
         * (非递归实现)查找"伸展树x"中键值为key的节点
         */
        private SplayTreeNode<T> iterativeSearch(SplayTreeNode<T> x, T key)
        {
            while (x != null)
            {
                int cmp = key.CompareTo(x.key);

                if (cmp < 0)
                    x = x.left;
                else if (cmp > 0)
                    x = x.right;
                else
                    return x;
            }

            return x;
        }

        public SplayTreeNode<T> iterativeSearch(T key)
        {
            return iterativeSearch(mRoot, key);
        }

        /* 
         * 查找最小结点:返回tree为根结点的伸展树的最小结点。
         */
        private SplayTreeNode<T> minimum(SplayTreeNode<T> tree)
        {
            if (tree == null)
                return null;

            while (tree.left != null)
                tree = tree.left;
            return tree;
        }

        public T minimum()
        {
            SplayTreeNode<T> p = minimum(mRoot);
            if (p != null)
                return p.key;

            return default(T);
        }

        /* 
         * 查找最大结点:返回tree为根结点的伸展树的最大结点。
         */
        private SplayTreeNode<T> maximum(SplayTreeNode<T> tree)
        {
            if (tree == null)
                return null;

            while (tree.right != null)
                tree = tree.right;
            return tree;
        }

        public T maximum()
        {
            SplayTreeNode<T> p = maximum(mRoot);
            if (p != null)
                return p.key;

            return default(T);
        }

        /* 
         * 旋转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);
        }

        /* 
         * 将结点插入到伸展树中,并返回根节点
         *
         * 参数说明:
         *     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
                {
                    Console.WriteLine("不允许插入相同节点(%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);
        }

        /* 
         * 删除结点(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);
        }

        /*
         * 销毁伸展树
         */
        private void destroy(SplayTreeNode<T> tree)
        {
            if (tree == null)
                return;

            if (tree.left != null)
                destroy(tree.left);
            if (tree.right != null)
                destroy(tree.right);

            tree = null;
        }

        public void clear()
        {
            destroy(mRoot);
            mRoot = null;
        }

        /*
         * 打印"伸展树"
         *
         * key        -- 节点的键值 
         * direction  --  0,表示该节点是根节点;
         *               -1,表示该节点是它的父结点的左孩子;
         *                1,表示该节点是它的父结点的右孩子。
         */
        private void print(SplayTreeNode<T> tree, T key, int direction)
        {

            if (tree != null)
            {

                if (direction == 0)    // tree是根节点
                    Console.WriteLine("{0} is root\n", tree.key);
                else                // tree是分支节点
                    Console.WriteLine("{0} is {1}'s {2} child\n", tree.key, key, direction == 1 ? "right" : "left");

                print(tree.left, tree.key, -1);
                print(tree.right, tree.key, 1);
            }
        }

        public void print()
        {
            if (mRoot != null)
                print(mRoot, mRoot.key, 0);
        }
    }

 

伸展树的测试程序

    public class SplayTreeTest
    {
        private int[] arr = { 10, 50, 40, 30, 20, 60 };

        public void test()
        {
            int i, ilen;
            SplayTree<int> tree = new SplayTree<int>();

            Console.WriteLine("== 依次添加: ");
            ilen = arr.Length;
            for (i = 0; i < ilen; i++)
            {
                Console.WriteLine(arr[i] + " ");
                tree.insert(arr[i]);
            }

            Console.WriteLine("\n== 前序遍历: ");
            tree.preOrder();

            Console.WriteLine("\n== 中序遍历: ");
            tree.inOrder();

            Console.WriteLine("\n== 后序遍历: ");
            tree.postOrder();
            Console.WriteLine();

            Console.WriteLine("== 最小值: " + tree.minimum());
            Console.WriteLine("== 最大值: " + tree.maximum());
            Console.WriteLine("== 树的详细信息: ");
            tree.print();

            i = 30;
            Console.WriteLine("\n== 旋转节点({0})为根节点\n", i);
            tree.splay(i);
            Console.WriteLine("== 树的详细信息: \n");
            tree.print();

            // 销毁二叉树
            tree.clear();
        }
    }

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

伸展树的测试程序

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

== 依次添加: 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旋转为根节点的示意图如下:


 

 
 
 

 

posted @ 2019-05-04 17:58  luanxm  阅读(342)  评论(0编辑  收藏  举报