树-Tree
来源:互联网 发布:删除mac管理员账户 编辑:程序博客网 时间:2024/05/31 05:28
作为基本的数据结构,树在各种算法题中或者现实中有得到广泛的应用。除了基本的普通二叉树,衍生出了二叉排序树、红黑树、B+/B树等等。这里我们对树的一些基本的操作以及高级应用进行总结。
一、 基本操作
既然是对树进行总结,那么首先要总结的就是树的遍历。树的遍历分为前序、中序、后序。
一般对于树的遍历,常见的是用递归实现。递归的实现,可以使得代码更加清晰易懂。但是递归这种操作在JVM中,假如递归层数太多,会占用栈的大部分空间。因此我们不仅要掌握递归操作,也需要掌握非递归操作。非递归操作可以查看我的另一篇博客
前序
递归
public void preOrder(TreeNode root ,List<TreeNode> list) { if(root == null) { return; } list.add(root); preOrder(root.left , list); preOrder(root.right , list);}
非递归
public List<TreeNode> preOrder(TreeNode root) { List<TreeNode> resultList = new ArrayList<TreeNode>(); if(root == null){ return resultList; } Deque<TreeNode> stack = new ArrayDeque<>(); TreeNode p = root; while(!stack.isEmpty() || p!=null) { if(p != null){ stack.push(p); resultList.add(p); p=p.left; }else { TreeNode temp = stack.pop(); p=temp.right; } }}
中序
递归
public void preOrder(TreeNode root ,List<TreeNode> list) { if(root == null) { return; } preOrder(root.left , list); list.add(root); preOrder(root.right , list);}
非递归
public List<TreeNode> preOrder(TreeNode root) { List<TreeNode> resultList = new ArrayList<TreeNode>(); if(root == null){ return resultList; } Deque<TreeNode> stack = new ArrayDeque<>(); TreeNode p = root; while(!stack.isEmpty() || p!=null) { if(p != null){ stack.push(p); p=p.left; }else { TreeNode temp = stack.pop(); resultList.add(p); p=temp.right; } }}
后序
递归
public void preOrder(TreeNode root ,List<TreeNode> list) { if(root == null) { return; } preOrder(root.left , list); preOrder(root.right , list); list.add(root);}
非递归
public List<TreeNode> preOrder(TreeNode root) { List<TreeNode> resultList = new ArrayList<TreeNode>(); if(root == null){ return resultList; } Deque<TreeNode> stack = new ArrayDeque<>(); TreeNode p = root; while(!stack.isEmpty() || p!=null) { if(p != null){ stack.push(p); result.addFirst(p.val); p=p.right; }else { TreeNode temp = stack.pop(); p=temp.left; } }}
二、遍历提升
在上述的非递归方法中,我们所占用了O(n)空间复杂度,但是其实还有占用O(1)的非递归遍历方法。这种方法就是Morris遍历。使用Morris遍历可以只需要占用O(1)的空间复杂度。但是他的运行也需要占用更多的时间,有时其实就是时间和空间的守恒转换。
在上面的非递归中,我们之所以需要占用O(n)的空间,是因为我们在遍历的过程中,需要回退到父节点。在Morris中,通过给叶节点添加左右儿子,就能实现回退到父节点的效果,但是用时增加了。
Talk is cheap ,show me the code.
中序
//首先介绍中序遍历。中序遍历是Morris的基本形式,前序和后续都是基于中序修改的public List<TreeNode> inOrder(TreeNode root) { List<TreeNode> result = new ArrayList<TreeNode>(); TreeNode cur = root; while(cur!=null){ if(cur.left == null){ result.add(cur); cur = cur.right; } else{ TreeNode pre = cur.left; while(pre.right!=null && pre.right!=cur){ pre = pre.right; } if(pre.right == null){ pre.right=cur; cur=cur.left; }else{ pre.right =null; result.add(cur); cur = cur.right; } } } return result;}//空间O(1),时间O(n)
前序
public List<TreeNode> inOrder(TreeNode root) { List<TreeNode> result = new ArrayList<TreeNode>(); TreeNode cur = root; while(cur!=null){ if(cur.left == null){ result.add(cur); cur = cur.right; } else{ TreeNode pre = cur.left; while(pre.right!=null && pre.right!=cur){ pre = pre.right; } if(pre.right == null){ result.add(cur); //唯一和中序的不同点 pre.right=cur; cur=cur.left; }else{ pre.right =null; cur = cur.right; } } } return result;}
后序
后序和前序和中序不一样,需要辅助存储。这里就不讨论了,思想都是一样的。有兴趣的同学可以自己操作一下。
三、高级二叉树
1. 二叉查找树(BST)
二叉查找树,其特点是一个节点左边的所有节点的值都小于自己,右边的所有节点都大于自己。同一个有序的序列,都可以生成不同的BST。在具体的应用中,对于BST的实现都是用非递归形式,会比较高效。
BST的运行时间完全取决于BST树的形状。最好的情况下,含有N个阶段的BST是一个完全平衡的二叉树,那么这样的时间是O(LgN),但是极端情况下,有可能得全部遍历为N。对于很多应用来树,插入得Key都是随机的,因此我们假设极端情况出现的比较少。
根据统计,由N个随即键构造的BST,插入或者查找未命中时平均比较次数为小于2InN(约1.39lgN)。可以看到BST比二分在查找上慢了39%,但是对于插入操作,插入一个新键BST会非常有优势,也是不到2lgN,而二分查找是线性的。当N越大,这个结论约接近。
在1979年J.Robson证明了,随机键构造的二叉查找树平均高度为树中节点数的对数级别,对于足够大的N,为2.99lgN。因此可以认为当N足够大且随机,树内路径不会超过3lgN。如果键不是随机,则可以用平衡二叉查找树。
2.平衡二叉树
首先我们要知道为什么会有平衡二叉树的存在。在BST中,我们知道BST的查找用时完全取决于二叉树的形状。因此在随机建树的过程中,可能出现最高为N的访问次数,因此不可控。那么如果控制二叉树的建立,尽量让BST变的平衡,那么就可以使得随机BST的访问时间都能为O(lgN)。这也就是平衡二叉树存在的原因。因此,奔着要让执行二叉树的查找、插入、删除等等操作都是对数级别这一目的,我们来讨论一下二叉平衡树。
首先提前明确一下,红黑树,是我们这里讨论的平衡二叉树的一种具体实现。
2-3节点树
我们上面讨论了二叉查找树,但是希望二叉查找树是尽量平衡的,因此我们引入2-3节点树的概念。2-3节点树的定义是树内包含2节点和3节点的树。那什么是2节点?
- 2-节点:普通的,包含一个键(A)和两个左右儿子的链接(X/Y)的节点就是2节点。
- 3-节点:是有两个键(A/B)和三个链接(X/Y/Z)。左边X是小于A的所有节点字树,Y是在AB之间的节点,Z是大于B的节点子树。
2-3节点树的性质使得2-3节点树就是平衡的。具体的2-3节点树的说明,可以参考这篇博客。总之,2-3树就是一个平衡树。而作为有序的平衡树,已经能够满足我们上面提到的“希望BST是平衡二叉树“的要求了。
那对于2-3平衡树的具体实现,就是我们了解到的红黑树了。红黑树的应用非常广泛,Java集合中的TreeSet和TreeMap,C++ STL中的set、map,以及Linux虚拟内存的管理,都是通过红黑树去实现的。红黑树和2-3节点树的关系是,在2-3节点树中,将3节点拆分为2个2节点,并用红链连接,这就是红黑树中的红点。黑链就是普通的节点,也就是黑点。在2-3节点转为红黑树的时候,需要遵循的原则是:
1.红链接均为左链接2.没有一个节点同时和两条红链接相连3.红黑树是完全黑树平衡的,也就是任意叶子到根的路径上黑链数量一致(将红链画成水平的)
这三条原则,也是知道红黑树在插入或者删除节点时,所需要执行的“旋转“操作的依据。
红黑树的代码这里就不贴上来了,感觉有点占篇幅。这里总结一下红黑树的性质。
1.大小为N的红黑树高度不超过2lgN。但是平均情况下,一般时1.00lgN。所以相对于上面的BST的平均1.39lgN,红黑树能够较少0.39的用时。
- 树Tree
- 树(Tree)
- 树tree
- 树-Tree
- B-Tree, B+Tree, B*树介绍
- 平衡树 balanced binary tree (AVL tree)
- B-Tree, B+Tree, B*树介绍
- Tree(2)--二叉树(Binary Tree)
- 【tree 反转二叉树 inverse binary tree】
- Splay Tree(伸展树)
- Splay Tree 伸展树
- tree 树 详解
- 字典树(trie tree)
- 后缀树【Suffix Tree】
- 二叉树Binary Tree
- B树 - balance tree
- 伸展树splay tree
- radix tree 基数树
- 类路径读取(加载)配置文件
- 如何使用付费版的PyCharm
- 注解
- error processing package libapache2-mod-php7.0 (–configure)
- java基础六(面向对象)
- 树-Tree
- hash函数以及冲突处理
- CCCC周六训练赛 数字母的题
- 从ES5语法到ES6语法你应该知道这些
- 在linux上做移动开发必须知道这五个
- 《快学Scala》习题详解 第12章 高阶函数
- 基于大数据的推荐算法研究(3)——层次结构
- AnimatedVectorDrawable的简单使用
- iptables--Netfilter components 相关的比较好的流程图(一)