树的遍历方式
- 前序遍历:前根遍历,按照根左右的顺序遍历
- 中序遍历:中根遍历,按照左根右的顺序遍历
- 后序遍历:后根遍历,按照左右根的顺序遍历
二叉排序树的概念
二叉排序树(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 {
@Test
public 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();
}
}