自平衡树--伸展树(Splay Tree)

Splay Tree介绍

BST树的插入、查询、删除的最大时间为O(n),此种情况出现在整个树伸展成只有一个分支。但是自平衡的AVL树和红-黑树的最大时间为O(logn)。
在计算机中。我们经常遇到一个问题:就是80%的访问只仅仅使用20%的数据。我们想在O(1)的时间内能访问到这20%的数据。这就是Splay Tree的主要使用场景,splay tree会把最近访问的项作为tree的跟节点,这使得最近访问的项可以在O(1)的时间内可以被再次访问
所有的Splay Tree的平均操作时间为O(logn),n是树的节点数。任何单一的操作在最糟糕的情况下可能需要O(n)。

Splay Tree搜索

splay tree的搜索操作同BST的搜索操作,但是除了搜索外,它还需要伸展(移动访问的节点到跟节点)。如果搜索成功,则把搜索到的节点移动到根节点。如果搜索不成功,则把搜索到的最后一个节点移动到跟节点。
在进行Splay Tree搜索时,可能有一下四种情况被遇到

  • 访问的节点就是跟元素
    我们直接返回跟节点的内容,不作任何处理。
  • 访问的节点是root节点的子节点
    此节点没有爷爷节点,此节点要么是root节点的左节点,我们右转。如果此节点是root节点的右节点,我们左转。
    这里写图片描述

  • 访问的节点含有父节点和爷爷节点。
    访问的节点是父节点的左孩子。父节点是爷爷节点的左孩子。我们进行两次右转。
    这里写图片描述

    • 访问的节点是父节点的右孩子。父节点时爷爷节点的右孩子。我们进行两次左转。
      这里写图片描述
    • 访问的节点是父节点的左孩子。而父节点是爷爷节点的右孩子。我们可以进行一次左旋、一次右旋:
      这里写图片描述

    • 访问的节点是父节点的右孩子。而父节点是爷爷节点的左孩子。我们可以进行一次右旋、一次左旋。
      这里写图片描述

Splay Tree java实现
public class SplayTree {
    private static class Node {
        private Node left;
        private Node right;
        private int val;

        public Node(int val) {
            this.val = val;
        }
    }

    /**
     * 节点树的插入
     * @param node
     * @param key
     */
    private static void insert (Node node ,int key) {

        if (key == node.val) {
            return ;
        }

        if (key < node.val) {
            if (node.left == null) {
                node.left = new Node(key);
            }
            else {
                insert(node.left,key);
            }
        }
        else if (key > node.val) {
            if (node.right == null) {
                node.right = new Node(key);
            } else {
                insert(node.right,key);
            }
        }
    }

    /**
     * 搜索节点
     * @param node
     * @param key
     * @return
     */
    public static Node search(Node node,int key) {
        if (node == null || node.val == key) {
            return node;
        }
        if (node.val > key) {
            //左节点为空,直接返回跟节点
            if (node.left == null) {
                return node;
            }

            if (node.left.val > key) {
                node.left.left = search (node.left.left,key);
                node = roateRight(node);
            }

            //处理左旋、右旋
            else if (node.left.val < key) {
                node.left.right = search(node.left.right,key);
                if (node.left.right != null) {
                    node.left = roateLeft(node.left);
                }
            }

            if (node.left != null) {
                return roateRight(node);
            }
            else {
                return node;
            }
        }
        else {
            if (node.right == null) {
                return node;
            }
            if (node.right.val < key) {
                node.right.right = search(node.right.right,key);
                node = roateLeft(node);
            }
            else if (node.right.val > key) {
                node.right.left = search(node.right.left,key);
                if (node.right.left != null) {
                    node.right = roateRight(node.right);
                }
            }
            if (node.right != null) {
                return roateLeft(node);
            } else {
                return node;
            }
        }
    }

    /**
     * 左旋实现
     * @param node
     * @return
     */
    private static Node roateLeft(Node node) {
        Node root = node.right;
        Node templeft = root.left;
        node.right = templeft;
        root.left = node;
        return root;
    }

    /**
     * 右旋实现
     * @param node
     * @return
     */
    private static Node roateRight(Node node) {
        Node root = node.left;
        Node tempRight = root.right;

        root.right = node;
        node.left = tempRight;

        return root;
    }

    /**
     * 前序遍历
     * @param root
     */
    private static void preNode(Node root) {
        if (root != null) {
            System.out.print(root.val +" ");
            preNode(root.left);
            preNode(root.right);
        }
    }

    /**
     * 层序遍历
     * @param args
     */
    private static void levelNode(Node root) {
        Queue<Node> queue = new LinkedList<SplayTree.Node>();
        queue.add(root);
        Node topNode = null;
        while (!queue.isEmpty()) {
            int size = queue.size();
            int i = 0;
            while (i < size) {
                topNode = queue.poll();
                System.out.print(topNode.val + " ");
                if (topNode.left != null) {
                    queue.add(topNode.left);
                }
                if (topNode.right != null) {
                    queue.add(topNode.right);
                }
                i = i + 1;
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        Node root = new Node(20);
        insert(root,10);
        insert(root,30);
        insert(root,5);
        root = search(root,10);
        levelNode(root);
    }
}
Splay Tree树的插入

在Splay Tree树中插入一个节点有以下几种情况:

  1. 如果根节点为null,则直接划分一个新的节点作为一个root节点并返回。
  2. 在Splay Tree中,如果对于给定的一个key在Splay Tree中存在,然后把此节点作为根节点。如果给定的一个key在Splay Tree中不存在,然后把最后访问的一个节点作为根节点返回。
  3. 如果要插入的节点的值跟root节点的值不同,则不做任何处理。
  4. 如果要插入的节点的值跟root节点的值不同,则划分新的节点然后跟root节点的值做比较:
    1> 如果插入的key 小于root的key。然后root作为新节点的右孩子,copy root节点的左孩子作为新节点的左孩子。并使root.left = null;
    2> 如果插入的key大于root的key。然后root作为新节点的左孩子,copy root节点的右孩子作为新节点的右孩子。并使得root.right = null;
  5. 返回新的节点作为根节点。
    public static Node insert(Node node,int key) {
        if (node == null) {
            return new Node(key);
        }


        node = search(node,key);

        if (key == node.val) {
            return node;
        }

        Node newNode = new Node(key);
        if (key > node.val) {
            newNode.left = node;
            newNode.right = node.right;
            node.right = null;
        }

        if (key < node.val) {
            newNode.right = node;
            newNode.left = node.left;
            node.left = null;
        }
        return newNode;
    }
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值