二叉树、红黑树
满二叉树:除叶子节点外,每个节点都有两个子节点
完全二叉树:叶子节点都在最底下两层,并且最后一层的叶子节点都靠左排列,除了最后一层,其它层的每个节点的子节点个数都要为2
完全二叉树有一个好处就是可以直接用数组存储,不需要额外的空间来存储左右叶子结点的引用;
平衡二叉树:左右子树的高度差小于等于1
二叉查找树(二叉搜索树):
左子节点的值小于父节点,右子节点的值大于父节点;
二叉查找树结合了数组和链表的优点,查询快、修改快(O(log(n))),但是存在的缺点是可能会有不平衡的情况
2-3树(为了解决二叉搜索树的不平衡的问题):
2-节点:一个非叶子节点有一个元素,则有两个子节点;
左子节点的所有元素小于该元素,右子节点的所有元素大于该元素;
3-节点:一个非叶子节点有两个元素(左<右),则有三个子节点;
左子节点的所有元素大于左边的元素,中间节点的所有元素大于左边的元素小于右边的元素,右子节点的所有元素大于右边的元素;
查找和插入的时间复杂度最差为:logn(当所有节点为3-节点时,时间复杂度log(3^n);当所有节点为2-节点时,时间复杂度log(2^n))
插入操作:如果插入后变成4-节点(三个元素),则这个4-节点要变成两个2-节点,并且中间的元素上移,直到满足规则;
2-3-4树:在2-3树的基础上多了4-节点:三个元素,四个子节点;
红黑树(自平衡的二叉查找树,不是严格平衡):
1、根节点是黑色
2、节点是红色或者黑色
3、红色节点的两个子节点必须是黑色的
4、叶子节点是黑色的空节点(NIL节点)
5、任意节点到其叶子节点的所有路径上的黑色节点数量相同
以上条件保证根节点到叶子结点的最长路径不会超过最短路径的两倍
AVL树(自平衡的二叉查找树,严格平衡)
B树:2-3树和2-3-4树都属于B树
m-阶B树:节点的子节点最大个数为m;2-3树是3阶B树,2-3-4树是4阶B树
B树的元素存在顺序关系,可以顺序查找;
有k个子节点的节点有k-1个键;
B树的所有节点都在同一层;
由于B树的节点元素个数有一定的范围,所以不需要频繁的自平衡操作,但是会浪费一些空间
B+树:
B树内部节点和叶子节点都会存储数据(导致节点存储的键的个数减少),B+树只有叶子节点会存储数据
B+树的叶子节点是相连的,并且叶子节点的键天然有序
二叉树的基本操作:
package BinaryTree; import java.util.*; public class BinarySearchTree { Node root = null; private class Node{ private int value; private Node right, left; public Node(int value){ this.value = value; } } // 查询节点 public boolean get(int value){ return get(root, value); } public boolean get(Node node, int value){ if(node==null) return false; int cmp = Integer.compare(value, node.value); if(cmp>0) get(node.right, value); else if(cmp<0) get(node.left, value); return true; } // 插入节点 public void put(int value){ root = put(root, value); } public Node put(Node node, int val){ if(node==null) return new Node(val); int cmp = Integer.compare(val, node.value); if(cmp>0) node.right = put(node.right, val); else if(cmp<0) node.left = put(node.left, val); return node; } // 删除节点,思路一:分三种情况讨论 // 没匹配到,只有一个子节点或者没有子节点,有两个子节点 public Node deleteMethodOne(Node root, int value){ if(root==null){ return null; } if(root.value>value){ root.left = deleteMethodOne(root.left, value); return root; }else if(root.value<value){ root.right = deleteMethodOne(root.right, value); return root; // 没找到则返回根节点 }else{ // 该节点有一个子节点,或者没有子节点 if(root.left==null){ return root.right; }else if(root.right==null){ return root.left; } // 该节点有两个子节点,则用右子树的最小节点替换,并且如果右子树的最小节点有右子树的话,则上移最小节点的右子树 Node minLeftNode = root.right; while(minLeftNode.left!=null){ minLeftNode = minLeftNode.left; } root.value = minLeftNode.value; root.right = deleteMinLeftNode(root.right); // 后继节点的右子树上移 return root; } } private Node deleteMinLeftNode(Node node){ if(node.left==null){ // 找到右子树的最小节点,并且返回最小节点的右子树 Node right = node.right; return right; } node.left = deleteMinLeftNode(node.left); return node; } // 思路二:匹配到的节点有两个子节点时,将此节点的左子树放到右子树的最小节点的左边 public Node deleteMethodTwo(Node root, int value){ if(root==null){ return null; } if(root.value>value){ root.left = deleteMethodTwo(root.left, value); }else if(root.value<value){ root.right = deleteMethodTwo(root.right, value); }else{ if(root.right==null){ return root.left; } else if(root.left==null){ return root.right; } else{ Node minLeftNode = root.right; while(minLeftNode.left!=null) minLeftNode = minLeftNode.left; // 将该节点的左子树放到右子树的最小左节点的左下方;这个方法会增加树的高度 minLeftNode.left = root.left; return root.right; // 右子树上移 } } return root; } // 查询树的最大深度 public int maxDepth(Node root){ if(root==null){ return 0; } return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1; // 每棵左右子树的最大深度加 1 } // 判断一棵二叉搜索树是不是平衡二叉树 // 自顶向下,先判断大树,再判断小树 public boolean isBalancedToB(Node root){ if(root==null){ return true; } int leftDepth = maxDepth(root.left); int rightDepth = maxDepth(root.right); if(Math.abs(leftDepth-rightDepth)>1) // 先判断树的高度差是否符合平衡二叉树,再判断左右两棵树是不是平衡二叉树 return false; return isBalancedToB(root.left) && isBalancedToB(root.right); // 如果有一棵子树不是平衡的直接退出 } // 自底向上,先判断小树,再判断大树 // 结束递归的条件,每一轮递归要做什么东西,要返回什么给上一级 // 节点为空的时候结束递归 判断子树的高度差 返回子树的高度 public boolean isBalancedBoT(Node root){ if(root==null) return true; return BoT(root)!=-1; } public int BoT(Node root){ if(root==null) return 0; int leftDepth = BoT(root.left); if (leftDepth==-1) return -1; // 有必要啊 int rightDepth = BoT(root.right); if (rightDepth==-1) return -1; if(Math.abs(leftDepth-rightDepth)>1) return -1; return Math.max(leftDepth, rightDepth) + 1; } // 递归 前序遍历 中序遍历 后序遍历 public void printFormer(Node root){ if (root==null) return; System.out.print(root.value + " "); printFormer(root.left); printFormer(root.right); } // DFS非递归打印二叉查找树 非递归前序遍历 public void printDFS(){ if (root==null) return; Stack<Node> stack = new Stack<>(); // 使用堆做DFS ArrayList<Integer> list = new ArrayList<>(); stack.push(root); while(!stack.empty()){ Node node = stack.pop(); list.add(node.value); if (node.right!=null) { // 要先把右边的压入栈,因为DFS先对左边的进行搜索,左边先出栈 stack.push(node.right); } if (node.left!=null) { stack.push(node.left); } } System.out.println(list); } // 非递归中序遍历 public void middleTraverse(){ List<Integer> list = new ArrayList<>(); Deque<Node> deque = new LinkedList<>(); Node node = root; while(node!=null || !deque.isEmpty()){ while(node!=null){ deque.push(node); node = node.left; } node = deque.pop(); list.add(node.value); node = node.right; // 弹出根结点后,把根结点的右结点赋给当前节点,继续对右结点进行搜索 } System.out.println(list); } // BFS打印二叉查找树 public void printBFS(){ if (root==null) return; Queue<Node> deque = new LinkedList<>(); // 使用队列做BFS,先进先出 ArrayList<Integer> list = new ArrayList<>(); deque.offer(root); while(!deque.isEmpty()){ Node node = deque.poll(); list.add(node.value); if(node.left!=null) deque.offer(node.left); if(node.right!=null) deque.offer(node.right); } System.out.println(list); } // BFS记录每层的节点 public List<List<Integer>> recordEachLayerNode(){ List<List<Integer>> listOuter = new ArrayList<>(); if(root==null) return listOuter; Queue<Node> queue = new LinkedList<>(); queue.offer(root); while(!queue.isEmpty()){ ArrayList<Integer> listInner = new ArrayList<>(); int size = queue.size(); // 每一层的节点个数 for(int i=0; i<size; i++){ Node node = queue.poll(); listInner.add(node.value); if(node.left!=null) queue.offer(node.left); if(node.right!=null) queue.offer(node.right); } listOuter.add(listInner); } return listOuter; } }
关于二叉树的题目:
1、二叉搜索树转换为一条链表 要求O(1)的空间复杂度 (后序遍历)
2、二叉树的最近公共祖先(后序遍历)