二叉树
- 若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值;
- 左、右子树也分别为二叉排序树。
也就是说,二叉排序树中,左子树都比节点小,右子树都比节点大,递归定义。
根据二叉排序树这个特点我们可以知道,二叉排序树的中序遍历一定是从小到大的,比如上图,中序遍历结果是:
1 3 4 6 7 8 10 13 14
性能取决于
- 最好的情况是 O(logn),存在于完全二叉排序树情况下,其访问性能近似于折半查找;
- 最差时候会是 O(n),比如插入的元素是有序的,生成的二叉排序树就是一个链表,这种情况下,需要遍历全部元素才行(见下图 b)。
public class Demo { public static void main(String[] args) { //乱序插入到二叉排序树中 BinarySearchTree binarySearchTree = new BinarySearchTree(null); binarySearchTree.insert(8); binarySearchTree.insert(3); binarySearchTree.insert(1); binarySearchTree.insert(6); binarySearchTree.insert(4); binarySearchTree.insert(7); binarySearchTree.insert(10); binarySearchTree.insert(13); binarySearchTree.insert(14); binarySearchTree.iterateMediumOrder(binarySearchTree.getRoot()); System.out.println(""); System.out.println(binarySearchTree.search(10).getData()); binarySearchTree.delete(6); binarySearchTree.iterateMediumOrder(binarySearchTree.getRoot()); } } class BinaryTreeNode{ private int data; private BinaryTreeNode leftChild; private BinaryTreeNode rightChild; public BinaryTreeNode(int data, BinaryTreeNode leftChild, BinaryTreeNode rightChild) { this.data = data; this.leftChild = leftChild; this.rightChild = rightChild; } public BinaryTreeNode() { } public int getData() { return data; } public void setData(int data) { this.data = data; } public BinaryTreeNode getLeftChild() { return leftChild; } public void setLeftChild(BinaryTreeNode leftChild) { this.leftChild = leftChild; } public BinaryTreeNode getRightChild() { return rightChild; } public void setRightChild(BinaryTreeNode rightChild) { this.rightChild = rightChild; } } class BinarySearchTree{ public BinaryTreeNode getRoot() { return root; } public void setRoot(BinaryTreeNode root) { this.root = root; } private BinaryTreeNode root; public BinarySearchTree(BinaryTreeNode root){ this.root=root; } /** * 前序遍历 * @param node */ public void iterateFirstOrder(BinaryTreeNode node){ if (node == null){ return; } System.out.println(node.getData()); iterateFirstOrder(node.getLeftChild()); iterateFirstOrder(node.getRightChild()); } /** * 中序遍历 * @param node */ public void iterateMediumOrder(BinaryTreeNode node){ if (node == null){ return; } iterateMediumOrder(node.getLeftChild()); System.out.println(node.getData()); iterateMediumOrder(node.getRightChild()); } /** * 后续遍历 * @param node */ public void iterateLastOrder(BinaryTreeNode node){ if (node == null){ return; } iterateLastOrder(node.getLeftChild()); iterateLastOrder(node.getRightChild()); System.out.println(node.getData()); } public BinaryTreeNode search(int data){ if(root==null||root.getData()==data){ return root; } if(data<root.getData()){ return search(root.getLeftChild(),data); }else{ return search(root.getRightChild(),data); } } /** * 二叉树查找 * 大于根节点 递归 左树 * 小于 递归右树 * 近似于二分查找 */ public BinaryTreeNode search(BinaryTreeNode root,int data){ if(root==null||root.getData()==data){ return root; } if(data<root.getData()){ return search(root.getLeftChild(),data); }else{ return search(root.getRightChild(),data); } } public BinaryTreeNode insert(int data){ if(root==null){ root=new BinaryTreeNode(); root.setData(data); return root; } return searchAndInsert(null,root,data); } /** * 先查找有没有整个元素,有的话就不用插入了,直接返回; * 没有就插入到之前查到(对比)好的合适的位置。 * 需要与父节点绑定 * @param parent 父节点 * @param node 当前比较节点 * @param data * @return */ public BinaryTreeNode searchAndInsert(BinaryTreeNode parent,BinaryTreeNode node,int data){ if(node==null){ node=new BinaryTreeNode(); node.setData(data); if(parent!=null){ if(data<parent.getData()){ parent.setLeftChild(node);//小于放在 左节点 }else{ parent.setRightChild(node);//大于放在右节点 } } return node; } if(node.getData()==data){ return node;//已经存在 }else if(data<node.getData()){//递归 return searchAndInsert(node,node.getLeftChild(),data); }else{ return searchAndInsert(node,node.getRightChild(),data); } } /** * 在指定节点下 查找指定数据节点的父亲节点 * * @param parent 当前比较节点的父节点 * @param node 当前比较的节点 * @param data 查找的数据 * @return */ public BinaryTreeNode searchParent(int data) { return searchParent(null, root, data); } public BinaryTreeNode searchParent(BinaryTreeNode parent, BinaryTreeNode node, int data) { if (node == null) { //比较的节点为空返回空 return null; } if (node.getData() == data) { //找到了目标节点,返回父节点 return parent; } else if (data < node.getData()) { //数据比当前节点小,左子树中递归查找 return searchParent(node, node.getLeftChild(), data); } else { return searchParent(node, node.getRightChild(), data); } } /** * 删除指定数据的节点 * * 删除 * * 如果要删除的节点正好是叶子节点,直接删除; * * 如果要删除的节点还有子节点,就需要建立父节点和子节点的关系: * * 如果只有左孩子或者右孩子,直接把这个孩子上移放到要删除的位置就好了; * * 如果有两个孩子,就需要选一个合适的孩子节点作为新的根节点,该节点称为 继承节点。 * * 新节点要求要比所有左子树大,比所有右子树小,怎么选择呢? * * **要比所有左子树的值大、右子树小,就从右子树里找最小的好了; * * 同样也可以从左子树里找最大的。** * * 两种选择方法都可以,本文选用右子树里最小的节点,也就是右子树中最左边的节点。 * * * */ public void delete(int data) { if (root == null || root.getData() == data) { //根节点为空或者要删除的就是根节点,直接删掉 root = null; return; } //在删除之前需要找到它的父亲 BinaryTreeNode parent = searchParent(data); if (parent == null) { //如果父节点为空,说明这个树是空树,没法删 return; } //接下来该找要删除的节点了 BinaryTreeNode deleteNode = search(parent, data); if (deleteNode == null) { //树中找不到要删除的节点 return; } //删除节点有 4 种情况 //1.左右子树都为空,说明是叶子节点,直接删除 if (deleteNode.getLeftChild() == null && deleteNode.getRightChild() == null) { //删除节点 deleteNode = null; //重置父节点的孩子状态,告诉他你以后没有这个儿子了 if (parent.getLeftChild() != null && parent.getLeftChild().getData() == data) { parent.setLeftChild(null); } else { parent.setRightChild(null); } return; } else if (deleteNode.getLeftChild() != null && deleteNode.getRightChild() == null) { //2.要删除的节点只有左子树,左子树要继承位置 if (parent.getLeftChild() != null && parent.getLeftChild().getData() == data) { parent.setLeftChild(deleteNode.getLeftChild()); } else { parent.setRightChild(deleteNode.getLeftChild()); } deleteNode = null; } else if (deleteNode.getRightChild() != null && deleteNode.getRightChild() == null) { //3.要删除的节点只有右子树,右子树要继承位置 if (parent.getLeftChild() != null && parent.getLeftChild().getData() == data) { parent.setLeftChild(deleteNode.getRightChild()); } else { parent.setRightChild(deleteNode.getRightChild()); } deleteNode = null; } else { //4.要删除的节点儿女双全,既有左子树又有右子树,需要选一个合适的节点继承,这里使用右子树中最左节点 BinaryTreeNode copyOfDeleteNode = deleteNode; //要删除节点的副本,指向继承节点的父节点 BinaryTreeNode heresNode = deleteNode.getRightChild(); //要继承位置的节点,初始为要删除节点的右子树的树根 //右子树没有左孩子了,他就是最小的,直接上位 if (heresNode.getLeftChild() == null) { //上位后,兄弟变成了孩子 heresNode.setLeftChild(deleteNode.getLeftChild()); } else { //右子树有左孩子,循环找到最左的,即最小的 while (heresNode.getLeftChild() != null) { copyOfDeleteNode = heresNode; //copyOfDeleteNode 指向继承节点的父节点 heresNode = heresNode.getLeftChild(); } //找到了继承节点,继承节点的右子树(如果有的话)要上移一位 copyOfDeleteNode.setLeftChild(heresNode.getRightChild()); //继承节点先继承家业,把自己的左右孩子变成要删除节点的孩子 heresNode.setLeftChild(deleteNode.getLeftChild()); heresNode.setRightChild(deleteNode.getRightChild()); } //最后就是确认位置,让要删除节点的父节点认识新儿子 if (parent.getLeftChild() != null && parent.getLeftChild().getData() == data) { parent.setLeftChild(heresNode); } else { parent.setRightChild(heresNode); } } } }
红黑树
https://blog.csdn.net/u011240877/article/details/53329023
红黑树本质上是一种二叉查找树,但它在二叉查找树的基础上额外添加了一个标记(颜色),同时具有一定的规则。这些规则使红黑树保证了一种平衡,插入、删除、查找的最坏时间复杂度都为 O(logn)。
它的统计性能要好于平衡二叉树(AVL树),因此,红黑树在很多地方都有应用。比如在 Java 集合框架中,很多部分(HashMap, TreeMap, TreeSet 等)都有红黑树的应用,这些集合均提供了很好的性能。
黑色高度:从根节点到叶节点的路径上黑色节点的个数,叫做树的黑色高度。
- 每个节点要么是红色,要么是黑色;
- 根节点永远是黑色的;
- 所有的叶节点都是是黑色的(注意这里说叶子节点其实是上图中的 NIL 节点);
- 每个红色节点的两个子节点一定都是黑色;
- 从任一节点到其子树中每个叶子节点的路径都包含相同数量的黑色节点;