直接上图吧,代码中有树形打印方法,图中有详细说明
代码实现如下:
package com.collonn.algorithm.tree; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; import java.util.Queue; /** * 二叉搜索树节点定义 */ class Node { // 节点值 public int value; // 节点左孩子 public Node left; // 节点右孩子 public Node right; // 节点的双亲 public Node parent; public Node() { } // 是否有左孩子 public boolean hasLeft() { return this.left == null ? false : true; } // 是否有右孩子 public boolean hasRight() { return this.right == null ? true : false; } // 是否只有一个孩子 public boolean hasOneSub() { return (this.left == null && this.right != null) || (this.left != null && this.right == null) ? true : false; } // 是否有两个孩子 public boolean hasTwoSub() { return (this.left != null && this.right != null ? true : false); } // 是否没有孩子 public boolean hasNonSub() { return (this.left == null && this.right == null) ? true : false; } // 是否叶子节点 public boolean isLeaf() { return this.hasNonSub(); } // 是否是ROOT节点 public boolean isRoot() { return (this.parent == null) ? true : false; } // 是否是双亲的左孩子 public boolean isLeft() { boolean flag = false; if (this.parent != null && (this == this.parent.left)) { flag = true; } return flag; } // 是否是双亲的右孩子 public boolean isRight() { boolean flag = false; if (this.parent != null && (this == this.parent.right)) { flag = true; } return flag; } // 该节点只能有一个孩子,返回这个孩子 public Node getOneSub() { if (this.left != null && this.right != null) { throw new RuntimeException("该节点只能有一个孩子。"); } return this.left == null ? this.right : this.left; } } /** * 二叉查找树(BST树)<br/> * 比节点小的元素放在节点的左子树中,比节点大的元素放在节点的右子树中<br/> */ public class BST { // 二叉根节点 public Node root; // 向二叉树插入新值 public void insert(int nv) { System.out.print("\n----------"); if (this.root == null) { this.root = new Node(); this.root.value = nv; this.root.left = null; this.root.right = null; this.root.parent = null; System.out.printf("\n插入根据节点:" + nv); return; } System.out.print("\nvisit:"); Node node = this.findPlace(nv); if (node != null) { if (nv < node.value) { System.out.printf("\n在%d的左子数中插入%d:", node.value, nv); node.left = new Node(); node.left.value = nv; node.left.left = null; node.left.right = null; node.left.parent = node; } else if (nv > node.value) { System.out.printf("\n在%d的右子数中插入%d:", node.value, nv); node.right = new Node(); node.right.value = nv; node.right.left = null; node.right.right = null; node.right.parent = node; } } } // 找到等值,返回null // 否则返回父节点,将新插入的node做为其孩子 public Node findPlace(int nv) { Node target = null; Node node = this.root; while (node != null) { System.out.print(node.value + ","); if (nv == node.value) { target = node; break; } if (nv < node.value) { if (node.left != null) { node = node.left; continue; } else { target = node; break; } } if (nv > node.value) { if (node.right != null) { node = node.right; continue; } else { target = node; break; } } } return target; } // 在二叉树中搜索某值 // 如果找到,则返回匹配节点,不见返回null public Node search(int v) { System.out.print("\n二叉树查找经过的节点:"); Node target = null; Node node = this.root; while (node != null) { System.out.print(node.value + ","); if (v == node.value) { target = node; break; } if (v < node.value && node.left != null) { if (node.left != null) { node = node.left; continue; } else { target = null; break; } } if (v > node.value) { if (node.right != null) { node = node.right; continue; } else { target = null; break; } } } return target; } // 先根遍历(根左右) public void dfsFirst(Node node) { if (node == null) { return; } // 访问根 System.out.print(node.value + ","); // 访问左子树 if (node.left != null) { this.dfsFirst(node.left); } // 访问右子树 if (node.right != null) { this.dfsFirst(node.right); } } // 中根遍历(左根右)(从小到大) public void dfsMid(Node node) { if (node == null) { return; } // 访问左子树 if (node.left != null) { this.dfsMid(node.left); } // 访问根 System.out.print(node.value + ","); // 访问右子树 if (node.right != null) { this.dfsMid(node.right); } } // ASC public void dfsASC(Node node) { this.dfsMid(node); } // DESC,(右根左)(从大到小): public void dfsDESC(Node node) { if (node == null) { return; } // 访问右子树 if (node.right != null) { this.dfsDESC(node.right); } // 访问根 System.out.print(node.value + ","); // 访问左子树 if (node.left != null) { this.dfsDESC(node.left); } } // 后根遍历(左右根) public void dfsLast(Node node) { if (node == null) { return; } // 访问左子树 if (node.left != null) { this.dfsLast(node.left); } // 访问右子树 if (node.right != null) { this.dfsLast(node.right); } // 访问根 System.out.print(node.value + ","); } // 二叉树的广度优先遍历,顺序输出 public void bfs() { System.out.print("\nBFS:"); Queue<Node> queue = new LinkedList<Node>(); queue.offer(this.root); Node node = null; while (!queue.isEmpty()) { // 删除队列头结点 node = queue.poll(); System.out.print(node.value + ","); // 将节点的左孩子加入队列 if (node.left != null) { queue.offer(node.left); } // 将节点的右孩子加入队列 if (node.right != null) { queue.offer(node.right); } } } // 二叉树的广度优先遍历,按照层次,从上到下,从左到右,打印此树 public void bfsLevel() { System.out.print("\n--------------- bfsLevel ---------------"); System.out.print("\nBFS:[节点值,是否叶子,双亲节点,层次]"); Queue<Node> queue = new LinkedList<Node>(); queue.offer(this.root); // 上一个节点的层次 int prevLevel = 0; Node node = null; while (!queue.isEmpty()) { // 删除队列头结点 node = queue.poll(); // 某节点在整棵树的第几层(ROOT为第1层) int levelOfFullTree = this.getLevelOfFullTree(node); // 如果当前深度比前一个节点的尝试大,则开始的新一层的节点访问 if (levelOfFullTree > prevLevel) { System.out.print("\n"); } System.out.print("["); System.out.print(node.value + ","); System.out.print((node.parent == null ? null : node.parent.value) + ","); System.out.print(prevLevel); System.out.print("]"); // System.out.printf("%3s," , node.value); // 将节点的左孩子加入队列 if (node.left != null) { queue.offer(node.left); } // 将节点的右孩子加入队列 if (node.right != null) { queue.offer(node.right); } prevLevel = levelOfFullTree; } } // 树形本层节点打印 private void printTreeLevel(int[] nodes) { System.out.print("\n|"); for (int j = 0; j < nodes.length; j++) { if (nodes[j] == 0) { // 打印两位数字的占位符 System.out.printf("--"); } else { // 打印节点 System.out.printf("%02d", nodes[j]); // 重置数组 nodes[j] = 0; } } System.out.print("|"); } /** * 二叉树的广度优先遍历,按照树形打印此树 * * <pre> * 算法用到的参数: * 1:二叉树的最大深度。 * 2:每个节点在二叉树中的层次Level,从1开始。 * 3:每个节点在该层中的序号indexOfLevel,从1开始。 * 注: * (1)Level和indexOfLevel可以在广度优先遍历时用计数器实现。 * (2)Level和indexOfLevel也可以在向树中插入新节点时,初始化到节点中。 * 如果用数组存储二叉树,假设父节点下标为i,则其左孩子的下标是2*i-1,右孩子的下标是2*i+1。 * * 算法基本思路: * (1):创建一个水平数组,水平数组的长度为 "满二叉树" 中的节点总数,将二叉树的所有节点,按满二叉树的样子,投影到水平数组上,每个节点在水平数组中都对就一个位置。 * (2):我们总结一下,对于每一个层级,映射到水平数组后,第一个节点的开始下标=s,本层任意相邻节点的步长(间距)=d,如果下所示 * 层级 起始下标 步长 * 1 2^3-1=7 2^4=16 * 2 2^2-1=3 2^3=8 * 3 2^1-1=1 2^2=4 * 4 2^0-1=0 2^1=2 * (3):有了以上数据,我们可以计算出,任意一层,任意一节点在水平数组中的下标, * 下标=起始下标+(该节点所在层次-1)*步长 * (4):OK,每一次每个节点的位置确定了,树形图自然也确定了。 * * 另: * 如果想要让输出特别规矩,我们必须: * 1:先确定每个节点的值(即输出的内容)最多占多少个字符宽度,假设为flength。 * 在输出树的过程中,不论遇到空值还是有值,都格式化输出,长度不足flength的,用空格补齐。 * 2:可以适当的将水平数组扩大一倍,这样每层中的各节点之间的距离拉长了,最终看到的结果是整个树水平放大了。 * </pre> */ public void bfsTree() { System.out.print("\n------------------ 树形打印开始 ------------------"); if (this.root == null) { System.out.print("\n树为pw"); return; } // 二叉树的高度 int maxLevel = this.getDepth(this.root); // 满二叉树时的总结点数 int fullTotal = (int) Math.pow(2, maxLevel) - 1; // 水平数组 int[] nodes = new int[fullTotal]; // 上一个节点的层次 int prevLevel = 1; // 每层的起始下标 int start = 0; // 每一层的元素的间距 int stepSize = 0; // 广度优先遍历 Queue<Node> queue = new LinkedList<Node>(); queue.offer(this.root); Node node = null; // 如果用数组存储二叉树,indexMap中存储各节点对应数组的下标 Map<Integer, Integer> indexMap = new HashMap<Integer, Integer>(); while (!queue.isEmpty()) { // 删除队列头结点 node = queue.poll(); // 某节点在整棵树的第几层(ROOT为第1层) int levelOfFullTree = this.getLevelOfFullTree(node); // 如果当前深度比前一个节点的尝试大,则开始的新一层的节点访问 if (levelOfFullTree > prevLevel) { // 打印层次的节点 this.printTreeLevel(nodes); } // 计算新的层次的开始下标与步长 start = (int) Math.pow(2, maxLevel - levelOfFullTree) - 1; stepSize = (int) Math.pow(2, maxLevel - levelOfFullTree + 1); // System.out.print("\n" + "start:" + start + ",stepSize:" + stepSize); // 记录节点的下标 int idx = -1; if (node == this.root) { indexMap.put(node.value, 1); idx = 1; } else { if (node == node.parent.left) { idx = 2 * indexMap.get(node.parent.value) - 1; } else if (node == node.parent.right) { idx = 2 * indexMap.get(node.parent.value); } indexMap.put(node.value, idx); } // 计算映射到水平数组的位置 int y = start + (idx - 1) * stepSize; nodes[y] = node.value; // System.out.print("\n" + "node.value:" + node.value + ",y:" + y); // 将节点的左孩子加入队列 if (node.left != null) { queue.offer(node.left); } // 将节点的右孩子加入队列 if (node.right != null) { queue.offer(node.right); } // 保留层次数,为下次循环使用 prevLevel = levelOfFullTree; } // 打印最底层的节点,因为while的推出,最底层次的节点没有打印,在这里单独打印 this.printTreeLevel(nodes); System.out.print("\n------------------ 树形打印结束 ------------------"); } // 计算以某节点为根的树的深度(从1开始) public int getDepth(Node node) { if (node == null) { return 0; } return 1 + Math.max(this.getDepth(node.left), this.getDepth(node.right)); } // 计算某节点在整棵树的第几层(ROOT为第1层) public int getLevelOfFullTree(Node node) { int depth = 0; Node t = node; while (t != null) { depth++; t = t.parent; } return depth; } // 计算以某节点为根的二叉树的总结点数(包含根节点) public int getTotalNode(Node node) { if (node == null) { return 0; } int L = getTotalNode(node.left); int R = getTotalNode(node.right); return 1 + (L + R); } // 以该节点为根的树,找树中最大的元素 public Node getMaxNode(Node node) { Node nodeMax = node; while (nodeMax != null) { if (nodeMax.right != null) { nodeMax = nodeMax.right; } else { break; } } return nodeMax; } // 以该节点为根的树,找树中最大的元素 public Node getMinNode(Node node) { Node nodeMax = node; while (nodeMax != null) { if (nodeMax.left != null) { nodeMax = nodeMax.left; } else { break; } } return nodeMax; } /** * <pre> * 找到目标节点并删除,返回目标节点 * 如果没找到,返回null * 注意,我们处理每一种分类时,总是先处理ROOT节点 * </pre> * * @param v * @return */ public Node delete(int v) { Node t = this.search(v); if (t == null) { return t; } // -------------------------------- 处理叶子节点,开始 -------------------------------- if (t.isLeaf()) { // t是叶子节点,且是ROOT if (t.isRoot()) { this.root = null; return t; } // t是叶子节点,且是双亲的左孩子,但非ROOT if (t.isLeft()) { t.parent.left = null; return t; } // t是叶子节点,且是双亲的右孩子,但非ROOT if (t.isRight()) { t.parent.right = null; return t; } } /** * -------------------------------- 处理有两个孩子的节点,开始 -------------------------------- * * <pre> * 如果目标节点t只有一个孩子,或有两个孩子,在这里我们统一处理,都当成有两个孩子。 * * t有两个孩子的情况处理比较复杂点,当我们删除t后,必须找到一个合适的节点来顶替t的位置。 * 这个节点的选择有两种方法。 * 方法1:从目标节点的左子树中选择值最大的节点,去顶替t的位置。 * 方法2:从目标节点的右子树中选择值最小的节点,顶替t的位置。 * * 我们在这里是这样处理的: * 先从t的左右子树中,选择深度最深的子树(如果t.left==null,则左子树的尝试为0)。 * 如果是t的左子树,选择值最大的节点。 * 如果是t的右子树,选择值最小的节点。 * 这样做可以尽量延迟二叉树出现极端不平衡的情况。 * * <pre> */ if (true) { int L = this.getDepth(t.left); int R = this.getDepth(t.right); // 新选出的节点 Node nm = null; if (L >= R) { // 从t的左子树中选择一个最大的 nm = this.getMaxNode(t.left); // 如果max是t的左子树的根,那么max一定没有右孩子 if (nm == t.left) { nm.parent = t.parent; nm.right = t.right; t.right.parent = nm; } // 如果max是t的左子树的最右侧的叶子结点,max没有左孩子,或,max有左孩子 if (nm.isRight()) { nm.parent.right = nm.left; if (nm.left != null) { nm.left.parent = nm.parent; } nm.left = t.left; if (nm.left != null) { nm.left.parent = nm; } nm.right = t.right; if (nm.right != null) { nm.right.parent = nm; } nm.parent = t.parent; } } else { // 从t的右子树中选择一个最小的 nm = this.getMinNode(t.right); // min是t的右子树的根,那么min一定没有左孩子 if (nm == t.right) { nm.parent = t.parent; nm.left = t.left; } // min是t的右子树的最左侧的叶子结点,min没有右孩子,或,min有右孩子 if (nm.isLeft()) { nm.parent.left = nm.right; if (nm.right != null) { nm.right.parent = nm.parent; } nm.left = t.left; if (nm.left != null) { nm.left.parent = nm; } nm.right = t.right; if (nm.right != null) { nm.right.parent = nm; } nm.parent = t.parent; } } // 调整选出的新节点与t的双亲节点的关系 if (t.isRoot()) { this.root = nm; } else { if (t == t.parent.left) { t.parent.left = nm; } else { t.parent.right = nm; } } } return t; } public static void main(String[] args) { // 初始化二叉查找树 BST bst = new BST(); bst.insert(50); bst.insert(30); bst.insert(80); bst.insert(10); bst.insert(40); bst.insert(35); bst.insert(90); bst.insert(85); bst.insert(5); bst.insert(15); bst.insert(20); bst.insert(13); bst.insert(3); bst.insert(8); bst.insert(37); bst.insert(70); bst.insert(60); bst.insert(75); bst.insert(78); bst.insert(72); bst.insert(95); bst.insert(99); bst.bfsLevel(); bst.bfsTree(); // int v = 35; // Node node = bst.search(v); // System.out.print("\ndepth:" + bst.getDepth(node)); // int k = bst.getMaxLevel(node); // System.out.print("\n树的深度,R:" + v + ",V:" + k); // // int k2 = bst.getTotalNode(node); // System.out.print("\n树总结点数,R:" + v + ",V:" + k2); // // Node nodeMax = bst.getMaxNode(node); // System.out.print("\n树中最大值,R:" + v + ",V:" + (nodeMax == null ? null : nodeMax.value)); // int value = 30; // bst.delete(value); // bst.bfsTree(); } }
原创博文,转载请注明出处。