树的遍历方式
- 前序遍历:前根遍历,按照根左右的顺序遍历
- 中序遍历:中根遍历,按照左根右的顺序遍历
- 后序遍历:后根遍历,按照左右根的顺序遍历
二叉排序树的概念
二叉排序树(Binary Sort Tree),又称二叉查找树(Binary Search Tree),也称二叉搜索树。
二叉排序树或者是一颗空树,或者是具有下列性质的二叉树:
- 若左子树不空,则左子树上所有结点的值均小于或等于它的根结点的值
- 若右子树不空,则右子树上所有结点的值均大与或等于它的根结点的值
- 左、右子树也分别为二叉排序树
- 二叉排序树中每次新插入的结点一定是叶子结点。
- 二叉排序树经过中序遍历后得到一个递增的有序序列。
- 二叉排序树中的元素不重复。
查找
由于二叉树的递归定义性质,二叉排序树的查找同样可以使用如下递归算法查找:
- 如果树是空的,则查找结束,无匹配。
- 如果被查找的值和根结点的值相等,查找成功。否则就在子树中继续查找。
- 如果被查找的值小于根结点的值就选择左子树,大于根结点的值就选择右子树。
在理想情况下,每次比较过后,树会被砍掉一半,近乎折半查找。使用中序遍历打印树,
打印出来的结果是从小到大的有序数组。
/*** 二叉排序树的搜索:* 如果存在则返回此结点,如果不存在则返回查找路径上最后一个结点** @param key* @param tree* @return*/public BinarySearchTree search(int key, BinarySearchTree tree) {if (tree == null) {return null;}if (key == tree.key) {return tree;} else if (key < tree.key) {return tree.left != null ? search(key, tree.left) : tree;} else {return tree.right != null ? search(key, tree.right) : tree;}}
插入
二叉排序的插入是建立在二叉排序的查找之上的,插入一个结点,就是通过查找发现该结点合适插入位置,把结点直接放进去。二叉排序树插入规则如下:
- 若查找的key已经有在树中,则point指向该数据结点。(二叉树的key不能重复)
- 若查找的Key没有在树中,则point指向查找路径上最后一个结点,根据情况插入最后一个结点的左子树或右子树。
图解:
如果插入 56,先根据查找算法发现树中存在则不做操作,
如果插入 54,先根据查找算法没有找到k=54的结点,返回point指向53结点,发现53是叶子结点,且54 > 53,则把54插入到53的右子结点。
/*** 二叉排序树的插入: 不插入重复的key** @param key* @return*/public void insert(int key) {BinarySearchTree sub = search(key, this);if (key != sub.key) {BinarySearchTree node = new BinarySearchTree(key);if (key > sub.key) {sub.right = node;} else {sub.left = node;}}}
删除
删除时需要考虑三种情况:
- 要删除的结点是叶子结点
- 要删除的结点只有左结点或右结点
- 要删除的结点既有左结点又有右结点
图解:
第一种情况:要删除的结点是叶子结点
删除的结点是叶子结点时(left = null, right = null),则直接把双亲结点的指针移除。如上图所示,如果删除的是12结点,则29结点的left = null
/*** 删除** @param key*/public void delete(int key) {BinarySearchTree node = search(key, this);if (node != null) {if (node.left == null && node.right == null) {BinarySearchTree parent = this.searchParent(key);if (parent.left != null && parent.left.equals(node)) {parent.left = null;}if (parent.right != null && parent.right.equals(node)) {parent.right = null;}}}}
第二种情况:要删除的结点只有左结点,这种情况下也分两种情况
- 要删除的结点为根结点,需要将根结点的root指针指向即将删除结点的左孩子,然后将删除结点的left置空即可

- 要删除的结点不为根结点,将父结点相应的孩子指针指向删除结点的左孩子,然后将删除结点的left置空

第三种情况:要删除的结点只有右结点,这种情况下也有两种情况同上

第四种情况:要删除的结点既有左结点又有右结点
查找删除结点右子树中最小的那个值(或者是左子树中最大的那个值),也就是右子树中位于最左方的那个结点(或者左子树中最右方的结点)。把查找的结点覆盖删除的结点。

/*** 删除** @param key*/public void delete(int key) {BinarySearchTree node = search(key, this);if (node != null) {BinarySearchTree parent = this.searchParent(key);// 叶子结点if (node.left == null && node.right == null) {if (parent == null) {// 只有一个根结点的树,不能再删除} else {if (parent.left != null && parent.left.equals(node)) {parent.left = null;}if (parent.right != null && parent.right.equals(node)) {parent.right = null;}}}// 只有左结点if (node.left != null && node.right == null) {if (parent == null) {// 此结点为根结点this.key = node.key;this.left = node.left;this.right = node.right;} else {if (parent.left != null && parent.left.equals(node)) {parent.left = node.left;}if (parent.right != null && parent.right.equals(node)) {parent.right = node.left;}}}// 只有右结点if (node.right != null && node.left == null) {if (parent == null) {// 此结点为根结点this.key = node.key;this.left = node.left;this.right = node.right;} else {if (parent.left != null && parent.left.equals(node)) {parent.left = node.right;}if (parent.right != null && parent.right.equals(node)) {parent.right = node.right;}}}// 既有左结点又有右结点if (node.right != null && node.left != null) {// 使用方法一:查找删除结点的右子树最小的结点,然后用查找的结点替换删除的结点BinarySearchTree min = node.right.getMin();this.delete(min.key);node.key = min.key;}}}
完整代码:
package com.bestlink.jh018782.tree;/*** 二叉排序树** @author jeffrey* @date 3/31/20 3:14 PM*/public class BinarySearchTree {private Integer key;private BinarySearchTree left;private BinarySearchTree right;public Integer getKey() {return key;}public BinarySearchTree getLeft() {return left;}public BinarySearchTree getRight() {return right;}public BinarySearchTree() {}public BinarySearchTree(int obj) {this.key = obj;}public BinarySearchTree(int[] data) {this.key = data[0];for (int i = 1; i < data.length; i++) {insert(data[i]);}}/*** 二叉排序树的搜索:* 如果存在则返回此结点,如果不存在则返回查找路径上最后一个结点** @param key* @param tree* @return*/public BinarySearchTree search(int key, BinarySearchTree tree) {if (tree == null) {return null;}if (key == tree.key) {return tree;} else if (key < tree.key) {return tree.left != null ? search(key, tree.left) : tree;} else {return tree.right != null ? search(key, tree.right) : tree;}}/*** 查找父结点*/public BinarySearchTree searchParent(int key) {if ((this.left != null && this.left.key == key) || (this.right != null && this.right.key == key)) {return this;} else {if (this.key > key && this.left != null) {return this.left.searchParent(key);} else if (this.key < key && this.right != null) {return this.right.searchParent(key);}}return null;}/*** 二叉排序树的插入: 不插入重复的key** @param key* @return*/public void insert(int key) {BinarySearchTree sub = search(key, this);if (key != sub.key) {BinarySearchTree node = new BinarySearchTree(key);if (key > sub.key) {sub.right = node;} else {sub.left = node;}}}/*** 删除** @param key*/public void delete(int key) {BinarySearchTree node = search(key, this);if (node != null) {BinarySearchTree parent = this.searchParent(key);// 叶子结点if (node.left == null && node.right == null) {if (parent == null) {// 只有一个根结点的树,不能再删除} else {if (parent.left != null && parent.left.equals(node)) {parent.left = null;}if (parent.right != null && parent.right.equals(node)) {parent.right = null;}}}// 只有左结点if (node.left != null && node.right == null) {if (parent == null) {// 此结点为根结点this.key = node.key;this.left = node.left;this.right = node.right;} else {if (parent.left != null && parent.left.equals(node)) {parent.left = node.left;}if (parent.right != null && parent.right.equals(node)) {parent.right = node.left;}}}// 只有右结点if (node.right != null && node.left == null) {if (parent == null) {// 此结点为根结点this.key = node.key;this.left = node.left;this.right = node.right;} else {if (parent.left != null && parent.left.equals(node)) {parent.left = node.right;}if (parent.right != null && parent.right.equals(node)) {parent.right = node.right;}}}// 既有左结点又有右结点if (node.right != null && node.left != null) {// 使用方法一:查找删除结点的右子树最小的结点,然后用查找的结点替换删除的结点BinarySearchTree min = node.right.getMin();this.delete(min.key);node.key = min.key;}}}/*** 递归查询结点的最左侧结点(最小的结点)* @return*/public BinarySearchTree getMin() {if (this.left == null) {return this;} else {return this.left.getMin();}}/*** 递归查询结点的最右侧结点(最大的结点)* @return*/public BinarySearchTree getMax() {if (this.right == null) {return this;} else {return this.right.getMax();}}/*** 获取树的深度** @param tree* @return*/public int getDepth(BinarySearchTree tree) {return tree == null ? 0 : (1 + Math.max(getDepth(tree.left), getDepth(tree.right)));}/*** 打印整颗树*/public void print() {// 得到树的深度int treeDepth = getDepth(this);// 最后一行的宽度为2的(n - 1)次方乘3,再加1// 作为整个二维数组的宽度int arrayHeight = treeDepth * 2 - 1;int arrayWidth = (2 << (treeDepth - 2)) * 3 + 1;// 用一个字符串数组来存储每个位置应显示的元素String[][] res = new String[arrayHeight][arrayWidth];// 对数组进行初始化,默认为一个空格for (int i = 0; i < arrayHeight; i++) {for (int j = 0; j < arrayWidth; j++) {res[i][j] = " ";}}// 从根节点开始,递归处理整个树// res[0][(arrayWidth + 1)/ 2] = (char)(root.val + '0');writeArray(this, 0, arrayWidth / 2, res, treeDepth);// 此时,已经将所有需要显示的元素储存到了二维数组中,将其拼接并打印即可for (String[] line : res) {StringBuilder sb = new StringBuilder();for (int i = 0; i < line.length; i++) {sb.append(line[i]);if (line[i].length() > 1 && i <= line.length - 1) {i += line[i].length() > 4 ? 2 : line[i].length() - 1;}}System.out.println(sb.toString());}}private static void writeArray(BinarySearchTree currNode, int rowIndex, int columnIndex, String[][] res, int treeDepth) {// 保证输入的树不为空if (currNode == null) {return;}// 先将当前节点保存到二维数组中res[rowIndex][columnIndex] = String.valueOf(currNode.key);// 计算当前位于树的第几层int currLevel = ((rowIndex + 1) / 2);// 若到了最后一层,则返回if (currLevel == treeDepth) {return;}// 计算当前行到下一行,每个元素之间的间隔(下一行的列索引与当前元素的列索引之间的间隔)int gap = treeDepth - currLevel - 1;// 对左儿子进行判断,若有左儿子,则记录相应的"/"与左儿子的值if (currNode.left != null) {res[rowIndex + 1][columnIndex - gap] = "/";writeArray(currNode.left, rowIndex + 2, columnIndex - gap * 2, res, treeDepth);}// 对右儿子进行判断,若有右儿子,则记录相应的"\"与右儿子的值if (currNode.right != null) {res[rowIndex + 1][columnIndex + gap] = "\\";writeArray(currNode.right, rowIndex + 2, columnIndex + gap * 2, res, treeDepth);}}}
测试用例:
package com.bestlink.jh018782;import com.bestlink.jh018782.tree.BinarySearchTree;import org.junit.jupiter.api.Test;/*** @author jeffrey* @date 3/31/20 3:56 PM*/public class BinarySearchTreeTest {@Testpublic void test() {int[] arr = new int[]{60, 48, 73, 29, 56, 95, 12, 33, 53, 86, 100};// 转换BinarySearchTree tree = new BinarySearchTree(arr);tree.print();System.out.println();// 插入tree.insert(90);tree.insert(31);tree.print();// 删除tree.delete(95);tree.print();}}
