`
324012406
  • 浏览: 24042 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
最近访客 更多访客>>
社区版块
存档分类
最新评论

Splay Tree

 
阅读更多

Splay Tree 是二叉查找树的一种,它与平衡二叉树、红黑树不同的是,Splay Tree从不强制地保持自身的平衡,每当查找到某个节点n的时候,在返回节点n的同时,Splay Tree会将节点n旋转到树根的位置,这样就使得Splay Tree天生有着一种类似缓存的能力,因为每次被查找到的节点都会被搬到树根的位置,所以当80%的情况下我们需要查找的元素都是某个固定的节点,或者是一部分特定的节点时,那么在很多时候,查找的效率会是O(1)的效率!当然如果查找的节点是很均匀地分布在不同的地方时,Splay Tree的性能就会变得很差了,但Splay Tree的期望的时间复杂度还是O(nlogn)的。

 

这里先介绍一下左旋(zag)和右旋(zig)的操作

然后就是Splay Tree进行Splay操作的具体步骤,主要分两种情况:

先看图中的左边,查找到的x节点的父节点是y,x是y的左子树,y的父节点z是根节点,y也是z的左子树,要把x旋转到根节点的位置,就要进行zig(y),然后再进行zig(x)操作

再看图中的右边,查找到的z节点的父节点是y,z是y的右子树,y的父节点x是根节点,y也是x的右子树,要把z旋转到根节点的位置,就要进行zag(y),然后进行zag(x)操作

若是途中的情况,若需要把x移动到根节点,则需要先进行zig(x),然后再进行zag(x)操作

还有一种y是z的左子树,x是y的右子树的情况,这时就需要先进行zag(x),然后再进行zig(x)操作了


下面就给出我自己写的Java版的Splay Tree的一种实现(为了方便,自己定义了类似于Map的接口,仅供参考):

Splay Tree Node

public class SplayTreeNode implements Serializable {
    private static final long serialVersionUID = 72651428543015658L;
    
    protected int key;
    protected Object value;
    protected SplayTreeNode father;
    protected SplayTreeNode leftChild;
    protected SplayTreeNode rightyChild;
    
    // get, set 方法
}

 Splay Tree 的接口

public interface SplayTree {
    /**
     * 添加/更新node结点
     * @param node
     * @return
     */
    public void put(SplayTreeNode node);

    /**
     * 根据key获取对应的node结点,若该结点不存在,则返回null
     * @param key
     * @return
     */
    public SplayTreeNode get(int key);

    /**
     * 根据key删除对应的node结点,若该结点不存在,则什么都不做
     * @param key
     */
    public void remove(int key);
    
    /**
     * 返回SplayTree中结点的个数
     * @return
     */
    public int size();
    
    /**
     * 显示SplayTree的树形结构
     */
    public void showTree();

    /**
     * 显示SplayTree各个结点的详细信息
     */
    public void showDetail();

 Splay Tree 的实现

public class DefaultSplayTree implements SplayTree, Serializable {
    private static final long serialVersionUID = 4967206515246041054L;
    
    protected int size;
    protected SplayTreeNode root;
    
    public DefaultSplayTree() {
        this.size = 0;
        this.root = null;
    }
    
    /* (non-Javadoc)
     * @see SplayTree#get(int)
     */
    @Override
    public SplayTreeNode get(int key) {       
        SplayTreeNode currentNode = this.root;
        while (true) {
            // 找不到对应的结点,返回null
            if (currentNode == null) {
                return null;
            }
            
            // 当前结点就是要找的结点
            if (key == currentNode.getKey()) {
                this.splay(currentNode);
                return currentNode;
            // key比当前结点的key要小
            } else if (key < currentNode.getKey()) {
                currentNode = currentNode.getLeftChild();
            // key比当前结点的key要大
            } else {
                currentNode = currentNode.getRightyChild();
            }
        }
    }

    /* (non-Javadoc)
     * @see SplayTree#put(SplayTreeNode)
     */
    @Override
    public void put(SplayTreeNode node) {
        if (node == null) {
            return;
        }
        
        // 当前的splay树为空
        if (this.root == null) {
            this.root = node;
            this.size ++;
            return;
        }
        
        // 当前结点
        SplayTreeNode currentNode = this.root;
        // 当前结点的父结点
        SplayTreeNode currentNodeFather = null;
        // 当前结点是否是父结点的左子树
        boolean isLeft = false;;
        while (true) {
            // 当前结点为null,则此位置为添加结点的插入位置
            if (currentNode == null) {
                node.setFather(currentNodeFather);
                if (isLeft) {
                    currentNodeFather.setLeftChild(node);
                } else {
                    currentNodeFather.setRightyChild(node);
                }
                this.size ++;
                this.splay(node);
                return;
            }      
            
            // 若当前结点的key比添加的结点的key要小,则在当前结点的左子树中查找
            if (node.getKey() < currentNode.getKey()) {
                currentNodeFather = currentNode;
                isLeft = true;
                currentNode = currentNode.getLeftChild();
                
            // 若当前结点的key比添加的结点的key要大,则在当前结点的右子树中查找
            } else if (node.getKey() > currentNode.getKey()) {
                currentNodeFather = currentNode;
                isLeft = false;
                currentNode = currentNode.getRightyChild();
            
            // 当前结点的key和要添加的结点的key相等,则更新该结点
            } else {
                currentNode.setValue(node.getValue());
                node = currentNode;
                this.splay(node);
                return;
            }
        } 
    }

    /* (non-Javadoc)
     * @see SplayTree#remove(int)
     */
    @Override
    public void remove(int key) {
        SplayTreeNode node = this.get(key);
        if (node != null) {
            this.join(node.getLeftChild(), node.getRightyChild());
            this.size --;
        }
    }
    
    /**
     * 对node结点进行右旋
     * @param node
     */
    protected void zig(SplayTreeNode node) {
        // 获取旋转结点的父结点
        SplayTreeNode father = node.getFather();
        
        // 将旋转结点的右子树设为父结点的左子树
        father.setLeftChild(node.getRightyChild());
        // 若旋转结点的右子树不为空,则将父结点设为右子树的父结点
        if (node.getRightyChild() != null) {
            node.getRightyChild().setFather(father);
        }     
        
        // 将父结点的父结点(爷爷结点)设为旋转结点的父结点
        node.setFather(father.getFather());
        // 若爷爷结点不为空
        if (father.getFather() != null) {
            // 若父结点为爷爷结点的左子树,则将旋转结点设为爷爷结点的左子树
            if (father == father.getFather().getLeftChild()) {
                father.getFather().setLeftChild(node);
                
            // 若父结点为爷爷结点的右子树,则将旋转结点设为爷爷结点的右子树
            } else {
                father.getFather().setRightyChild(node);
            }
        } 
        
        // 将父结点设为旋转结点的右子树
        node.setRightyChild(father);
        // 更新父结点的父结点为旋转结点
        father.setFather(node);
    }
    
    /**
     * 对node结点进行左旋
     * @param node
     */
    protected void zag(SplayTreeNode node) {
        // 获取旋转结点的父结点
        SplayTreeNode father = node.getFather();
        
        // 将旋转结点的左子树设为父结点的右子树
        father.setRightyChild(node.getLeftChild());
        // 若旋转结点的左子树不为空,则将父结点设为左子树的父结点
        if (node.getLeftChild() != null) {
            node.getLeftChild().setFather(father);
        }
        
        // 将父结点的父结点(爷爷结点)设为旋转结点的父结点
        node.setFather(father.getFather());
        // 若爷爷结点不为空
        if (father.getFather() != null) {
            // 若父结点为爷爷结点的左子树,则将旋转结点设为爷爷结点的左子树
            if (father == father.getFather().getLeftChild()) {
                father.getFather().setLeftChild(node);
            
            // 若父结点为爷爷结点的右子树,则将旋转结点设为爷爷结点的右子树
            } else {
                father.getFather().setRightyChild(node);
            }
        }
        
        // 将父结点设为旋转结点的左子树
        node.setLeftChild(father);
        // 更新父结点的父结点为旋转结点
        father.setFather(node);
    }
    
    /**
     * 对node结点进行伸展
     * @param node
     */
    protected void splay(SplayTreeNode node) {
        if (this.root == null) {
            return;
        }
        
        while (node.getFather() != null) {
            if (node.getFather() == this.root) {
                if (node == node.getFather().getLeftChild()) {
                    this.zig(node);
                } else {
                    this.zag(node);
                }
            } else if (node.getFather().getFather().getLeftChild() == node.getFather()) {
                if (node == node.getFather().getLeftChild()) {
                    this.zig(node.getFather());
                    this.zig(node);
                } else {
                    this.zag(node);
                    this.zig(node);
                }
            } else {
                if (node == node.getFather().getRightyChild()) {
                    this.zag(node.getFather());
                    this.zag(node);
                } else {
                    this.zig(node);
                    this.zag(node);
                }
            }
        }
        this.root = node;
    }
    
    /**
     * 合并treeA, treeB,更新root
     * @param treeA
     * @param treeB
     */
    protected void join(SplayTreeNode treeA, SplayTreeNode treeB) {
        if (treeA != null) {
            treeA.setFather(null);
        }
        if (treeB != null) {
            treeB.setFather(null);
        }
        
        if (treeA == null) {
            this.root = treeB;
            return;
        }
        if (treeB == null) {
            this.root = treeA;
            return;
        }
        
        SplayTreeNode node = treeA;
        while (node.getRightyChild() != null) {
            this.splay(node.getRightyChild());
            node = node.getRightyChild();
        }
        
        node.setRightyChild(treeB);
        treeB.setFather(node);
        this.root = node;
    }

    /* (non-Javadoc)
     * @see SplayTree#size()
     */
    @Override
    public int size() {
        return this.size;
    }
    
    /* (non-Javadoc)
     * @see SplayTree#showTree()
     */
    @Override
    public void showTree() {
        List<SplayTreeNode> nodeList = new ArrayList<SplayTreeNode>();
        nodeList.add(this.root);
        this.bfs(nodeList);     
    }
    
    /**
     * 广度优先遍历SplayTree的每个结点,若结点不为空则输出key值,若为空则输出*
     * @param nodeList
     */
    protected void bfs(List<SplayTreeNode> nodeList) {
        List<SplayTreeNode> newNodeList = new ArrayList<SplayTreeNode>();
        boolean found = false;
        for (SplayTreeNode node : nodeList) {
            if (node != null) {
                found = true;
                System.out.print(node.getKey() + " ");
                newNodeList.add(node.getLeftChild());
                newNodeList.add(node.getRightyChild());
            } else {
                System.out.print("* ");
                newNodeList.add(null);
                newNodeList.add(null);
            }
        }
        System.out.println("");
        
        if (found) {
            this.bfs(newNodeList);
        }
    }
    
    /* (non-Javadoc)
     * @see SplayTree#showDetail()
     */
    @Override
    public void showDetail() {
        this.inorderTraversal(this.root);   
    }
    
    /**
     * 中序遍历SplayTree的每个结点,并输出结点的详细信息
     * @param node
     */
    protected void inorderTraversal(SplayTreeNode node) {
        if (node != null) {
            System.out.println(node.toString());
            this.inorderTraversal(node.getLeftChild());
            this.inorderTraversal(node.getRightyChild());
        }
    }
}
 
  • 大小: 19 KB
  • 大小: 17.3 KB
  • 大小: 12.9 KB
0
1
分享到:
评论

相关推荐

    MTPA数值求解:双法探究,MTPA数值求解详解:两种方法的比较与应用探索,MTPA数值求解两种方法 ,MTPA数值求解; 方法一; 方法二;,MTPA数值求解的两种高效方法

    MTPA数值求解:双法探究,MTPA数值求解详解:两种方法的比较与应用探索,MTPA数值求解两种方法 ,MTPA数值求解; 方法一; 方法二;,MTPA数值求解的两种高效方法

    为什么你的switch总出bug?90%新手不知道的break语句隐藏规则.pdf

    # 踏入C语言的奇妙编程世界 在编程的广阔宇宙中,C语言宛如一颗璀璨恒星,以其独特魅力与强大功能,始终占据着不可替代的地位。无论你是编程小白,还是有一定基础想进一步提升的开发者,C语言都值得深入探索。 C语言的高效性与可移植性令人瞩目。它能直接操控硬件,执行速度快,是系统软件、嵌入式开发的首选。同时,代码可在不同操作系统和硬件平台间轻松移植,极大节省开发成本。 学习C语言,能让你深入理解计算机底层原理,培养逻辑思维和问题解决能力。掌握C语言后,再学习其他编程语言也会事半功倍。 现在,让我们一起开启C语言学习之旅。这里有丰富教程、实用案例、详细代码解析,助你逐步掌握C语言核心知识和编程技巧。别再犹豫,加入我们,在C语言的海洋中尽情遨游,挖掘无限可能,为未来的编程之路打下坚实基础!

    避坑指南:DeepSeekAPI常见错误代码解析与解决方案.pdf

    在日常的工作和学习中,你是否常常为处理复杂的数据、生成高质量的文本或者进行精准的图像识别而烦恼?DeepSeek 或许就是你一直在寻找的解决方案!它以其高效、智能的特点,在各个行业都展现出了巨大的应用价值。然而,想要充分发挥 DeepSeek 的优势,掌握从入门到精通的知识和技能至关重要。本文将从实际应用的角度出发,为你详细介绍 DeepSeek 的基本原理、操作方法以及高级技巧。通过系统的学习,你将能够轻松地运用 DeepSeek 解决实际问题,提升工作效率和质量,让自己在职场和学术领域脱颖而出。现在,就让我们一起开启这场实用又高效的学习之旅吧!

    视频缩略图生成组件,图像格式转换另存为jpg, png, gif, bmp格式

    'Function 生成视频缩略图(ByVal 视频文件 As String, ByVal 保存缩略图的文件路径 As String, Optional ByVal jpg图像品质 As Long = 80, _ ' Optional ByVal 缩略图宽度 As Long = 500, Optional ByVal 缩略图高度 As Long = 500 _ ' , Optional 返回图像实际宽度 As Long, Optional 返回图像实际高度 As Long) As Boolean Public Function SaveImageAs(LoadImgFile As String, ByVal SaveAsImgFile As String, _ Optional ByVal JpgQuality As Long = 80, Optional hPal As Long, Optional Resolution As Single) As Boolean

    浏览器插件开发:基于DeepSeekAPI的沉浸式翻译工具实战.pdf

    在日常的工作和学习中,你是否常常为处理复杂的数据、生成高质量的文本或者进行精准的图像识别而烦恼?DeepSeek 或许就是你一直在寻找的解决方案!它以其高效、智能的特点,在各个行业都展现出了巨大的应用价值。然而,想要充分发挥 DeepSeek 的优势,掌握从入门到精通的知识和技能至关重要。本文将从实际应用的角度出发,为你详细介绍 DeepSeek 的基本原理、操作方法以及高级技巧。通过系统的学习,你将能够轻松地运用 DeepSeek 解决实际问题,提升工作效率和质量,让自己在职场和学术领域脱颖而出。现在,就让我们一起开启这场实用又高效的学习之旅吧!

    bkall_answers(2).json

    bkall_answers(2).json

    从零实现多轮对话:DeepSeek上下文推理模式开发手册.pdf

    在日常的工作和学习中,你是否常常为处理复杂的数据、生成高质量的文本或者进行精准的图像识别而烦恼?DeepSeek 或许就是你一直在寻找的解决方案!它以其高效、智能的特点,在各个行业都展现出了巨大的应用价值。然而,想要充分发挥 DeepSeek 的优势,掌握从入门到精通的知识和技能至关重要。本文将从实际应用的角度出发,为你详细介绍 DeepSeek 的基本原理、操作方法以及高级技巧。通过系统的学习,你将能够轻松地运用 DeepSeek 解决实际问题,提升工作效率和质量,让自己在职场和学术领域脱颖而出。现在,就让我们一起开启这场实用又高效的学习之旅吧!

    安全防护全攻略:DeepSeekAPI密钥管理与请求限流最佳实践.pdf

    在日常的工作和学习中,你是否常常为处理复杂的数据、生成高质量的文本或者进行精准的图像识别而烦恼?DeepSeek 或许就是你一直在寻找的解决方案!它以其高效、智能的特点,在各个行业都展现出了巨大的应用价值。然而,想要充分发挥 DeepSeek 的优势,掌握从入门到精通的知识和技能至关重要。本文将从实际应用的角度出发,为你详细介绍 DeepSeek 的基本原理、操作方法以及高级技巧。通过系统的学习,你将能够轻松地运用 DeepSeek 解决实际问题,提升工作效率和质量,让自己在职场和学术领域脱颖而出。现在,就让我们一起开启这场实用又高效的学习之旅吧!

    实时对话系统构建:WebSocket长连接下的API稳定性保障方案.pdf

    在日常的工作和学习中,你是否常常为处理复杂的数据、生成高质量的文本或者进行精准的图像识别而烦恼?DeepSeek 或许就是你一直在寻找的解决方案!它以其高效、智能的特点,在各个行业都展现出了巨大的应用价值。然而,想要充分发挥 DeepSeek 的优势,掌握从入门到精通的知识和技能至关重要。本文将从实际应用的角度出发,为你详细介绍 DeepSeek 的基本原理、操作方法以及高级技巧。通过系统的学习,你将能够轻松地运用 DeepSeek 解决实际问题,提升工作效率和质量,让自己在职场和学术领域脱颖而出。现在,就让我们一起开启这场实用又高效的学习之旅吧!

    结构化输出进阶指南:深度解析DeepSeekAPI响应数据处理技巧.pdf

    在日常的工作和学习中,你是否常常为处理复杂的数据、生成高质量的文本或者进行精准的图像识别而烦恼?DeepSeek 或许就是你一直在寻找的解决方案!它以其高效、智能的特点,在各个行业都展现出了巨大的应用价值。然而,想要充分发挥 DeepSeek 的优势,掌握从入门到精通的知识和技能至关重要。本文将从实际应用的角度出发,为你详细介绍 DeepSeek 的基本原理、操作方法以及高级技巧。通过系统的学习,你将能够轻松地运用 DeepSeek 解决实际问题,提升工作效率和质量,让自己在职场和学术领域脱颖而出。现在,就让我们一起开启这场实用又高效的学习之旅吧!

    清华出品AI教程(15天熟练掌握DeepSeek)

    近日,一份由清华大学团队发布《DeepSeek:从入门到精通》的AI学习教程冲上了热搜,它是由清华大学新闻与传播学院新媒体研究中心元宇宙文化实验室的余梦珑博士后及其团队倾力打造,从三个方面深入剖析了DeepSeek,DeepSeek是什么?有什么用?怎么使用? 详细论述了其应用场景与使用方法,并讲解了如何通过设计精妙的提示语来提升AI的使用效率,以及丰富的实例干货。 全部104页,完整版资料已经帮大家整理好了,免费领取 资料链接: https://pan.quark.cn/s/be3b500c539c

    异常处理大全:DeepSeekAPI调用中的10个常见错误与修复方案.pdf

    在日常的工作和学习中,你是否常常为处理复杂的数据、生成高质量的文本或者进行精准的图像识别而烦恼?DeepSeek 或许就是你一直在寻找的解决方案!它以其高效、智能的特点,在各个行业都展现出了巨大的应用价值。然而,想要充分发挥 DeepSeek 的优势,掌握从入门到精通的知识和技能至关重要。本文将从实际应用的角度出发,为你详细介绍 DeepSeek 的基本原理、操作方法以及高级技巧。通过系统的学习,你将能够轻松地运用 DeepSeek 解决实际问题,提升工作效率和质量,让自己在职场和学术领域脱颖而出。现在,就让我们一起开启这场实用又高效的学习之旅吧!

    冰点下载器珍藏版.zip

    冰点下载器珍藏版.zip

    Wallpaper Engine 壁纸一键提取

    Wallpaper Engine 是一款广受欢迎的动态壁纸软件,允许用户将各种动态、交互式壁纸应用到桌面上。其丰富的创意工坊内容让用户可以轻松下载和分享个性化的壁纸。而“一键提取”功能则是 Wallpaper Engine 中一个非常实用的工具,能够帮助用户快速提取和保存壁纸资源,方便后续使用或分享。

    基于碳达峰碳中和战略目标的城市园林绿化碳汇系统建设.pdf

    科研人员

    成本降低90%!DeepSeek与LangChain联动的API调用效率优化实验.pdf

    在日常的工作和学习中,你是否常常为处理复杂的数据、生成高质量的文本或者进行精准的图像识别而烦恼?DeepSeek 或许就是你一直在寻找的解决方案!它以其高效、智能的特点,在各个行业都展现出了巨大的应用价值。然而,想要充分发挥 DeepSeek 的优势,掌握从入门到精通的知识和技能至关重要。本文将从实际应用的角度出发,为你详细介绍 DeepSeek 的基本原理、操作方法以及高级技巧。通过系统的学习,你将能够轻松地运用 DeepSeek 解决实际问题,提升工作效率和质量,让自己在职场和学术领域脱颖而出。现在,就让我们一起开启这场实用又高效的学习之旅吧!

    13考试真题最近的t66.txt

    13考试真题最近的t66.txt

    对外承包项目借款合同2[示范文本].doc

    对外承包项目借款合同2[示范文本].doc

    性能对比测评:DeepSeek-V3与GPT-4接口的响应速度实测.pdf

    在日常的工作和学习中,你是否常常为处理复杂的数据、生成高质量的文本或者进行精准的图像识别而烦恼?DeepSeek 或许就是你一直在寻找的解决方案!它以其高效、智能的特点,在各个行业都展现出了巨大的应用价值。然而,想要充分发挥 DeepSeek 的优势,掌握从入门到精通的知识和技能至关重要。本文将从实际应用的角度出发,为你详细介绍 DeepSeek 的基本原理、操作方法以及高级技巧。通过系统的学习,你将能够轻松地运用 DeepSeek 解决实际问题,提升工作效率和质量,让自己在职场和学术领域脱颖而出。现在,就让我们一起开启这场实用又高效的学习之旅吧!

    基于java捷邻小程序.zip

    借助java编程语言和MySQL数据库等实现系统的全部功能,接下来对系统进行测试,测试系统是否有漏洞和测试用户权限来完善系统,最终系统完成达到相关标准。 本系统主要包括管理员和用户两个角色;主要包括首页、个人中心、用户管理、商品分类管理、商品信息管理、促销产品管理、系统管理、订单管理等功能的管理系统。 系统权限按管理员和用户这两类涉及用户划分。 (1)管理员功能需求 管理员登陆后,主要包括首页、个人中心、用户管理、商品分类管理、商品信息管理、促销产品管理、系统管理、订单管理等功能。 2)用户功能需求 用户登陆后进入小程序首页,可以实现首页、商品信息、促销产品、购物车、我的等,在我的页面可以对个人中心、我的收藏管理、用户充值、购物车、我的订单等功能进行详细操作。

Global site tag (gtag.js) - Google Analytics