直接上图吧,代码中有树形打印方法,图中有详细说明
代码实现如下:
- 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();
- }
- }
原创博文,转载请注明出处。
http://blog.csdn.net/collonn/article/details/18732079