二叉搜索树(Binaru Search Tree)
性质
- 二叉搜索树是二叉树的一种,应用非常广泛,简称BST
- 任意一个节点的值都大于其左子数所有节点的值
- 任意一个节点的值都小于其右子树所有节点的值
- 它的左右子树也是一个二叉搜索树
需求:
- 可以存储任何元素
- 自定义类型,需要指定比较方式
- 不能为null
新建一个BinarySearchTree.java文件
定义节点
private static class Node<E> {
E element;
Node<E> left;
Node<E> right;
Node<E> parent;
public Node(E element, Node<E> parent) {
this.element = element;
this.parent = parent;
}
/**
* 是否为叶子节点
* @return
*/
public boolean isLeaf() {
return left == null && right == null;
}
/**
* 度为2的结点
* @return
*/
public boolean hasTwoDeg() {
return left != null && right != null;
}
}
构造函数以及所需成员变量
public class BinarySearchTree<E> {
private int size; //容量
private Node<E> root; //根节点
private Comparator<E> comparator; //比较器
public BinarySearchTree() {
this(null);
}
public BinarySearchTree(Comparator<E> comparator) {
this.comparator = comparator;
}
}
添加节点思路
- 判断添加的元素是否为空,为空直接抛出异常
- 如果是添加第一个节点(直接判断根节点是否为空)
- 为空就直接new一个节点,parent为null,size++即可
- 如果不是添加第一个节点
- 先找到父节点 (利用二叉搜索树的性质)
- 判断添加的节点和当前节点元素的大小,
- 大于就往右边继续查找
- 小于就往左边查找
- 等于这里只做覆盖处理,不做其他处理
/**
* 添加元素
* @param element
*/
public void add(E element) {
if (element == null ) {
throw new IllegalArgumentException("元素不能为空");
}
//添加第一个节点
if (root == null) {
root = new Node<>(element, null);
size++;
return;
}
//其它节点
Node<E> parent = root; //记录父亲节点
Node<E> node = root; //用于循环查找
int cmp = 0;
do {
cmp = compare(element, node.element);
parent = node;
if (cmp > 0) {
node = node.right;
}else if (cmp < 0) {
node = node.left;
}else {
//相等
node.element = element; //节点值覆盖
return;
}
}while (node != null);
Node<E> newNode = new Node<>(element, parent);
if (cmp > 0){
parent.right = newNode;
}else {
parent.left = newNode;
}
size++;
}
比较函数
compare(E element, E element1)
- 对于自定义类型,需要指定比较方式,所以需要用到Comparator
- 允许外界传入一个 Comparator 自定义比较方案
- 如果没有传入 Comparator,强制认定元素实现了 Comparable 接口
//比较元素大小
private int compare(E element, E element1) {
if (comparator != null) {
return comparator.compare(element, element1);
}
return ((Comparable<E>) element).compareTo(element1);
}
使用方法
compareTo()
自定义一个Student类用age的大小来存放,需要重写compareTo函数
@Override
public int compareTo(Person o) {
return this.age - o.age;
}
comparator()
使用匿名内部类实现
BinarySearchTree<Student> bst = new BinarySearchTree<>(new Comparator<Student>() {
public int compare(Studento1, Studento2) {
return o2.getAge() - o1.getAge();
}
});
遍历二叉树
前序遍历(递归)
- 访问顺序
- 根节点 > 左节点 > 右节点
public void preorderTraversal() {
preorderTraversal(root);
}
public void preorderTraversal(Node<E> node) {
if (node == null) return;
System.out.println(node.element);
preorderTraversal(node.left);
preorderTraversal(node.right);
}
前序遍历(非递归思想)
- 利用栈实现
- 将root入栈
- 循环下面操作
- 弹出栈顶节点top,
- 将top.right入栈
- 将top.left入栈
中序遍历(递归)
- 访问顺序
- 左节点 > 根节点 > 右节点
//中序遍历
public void inorderTraversal() {
inorderTraversal(root);
}
public void inorderTraversal(Node<E> node) {
if (node == null) return;
inorderTraversal(node.left);
System.out.println(node.element);
inorderTraversal(node.right);
}
中序遍历(非递归思想)
- 利用栈实现
- 设置 node = root
- 循环执行以下操作
- 如果 node != null
- 将 node 入栈
- 设置 node = node.left
- 如果 node == null
- 如果栈为空,结束遍历
- 如果栈不为空,弹出栈顶元素并赋值给 node
- 对 node 进行访问
- 设置 node = node.right
/**
* 不使用递归解决
*/
public List<Integer> inorderTraversal2(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
List<Integer> result = new ArrayList<>();
if (root == null) return result;
TreeNode cur = root;
while (cur != null || !stack.empty()) {
if (cur != null) {
stack.push(cur);
cur = cur.left;
}else {
cur = stack.pop();
result.add(cur.val);
cur = cur.right;
}
}
return result;
}
后序遍历(递归)
- 访问顺序
- 左节点 > 右节点 > 根节点
//后序遍历
public void postorderTraversal() {
postorderTraversal(root);
}
public void postorderTraversal(Node<E> node) {
if (node == null) return;
postorderTraversal(node.left);
postorderTraversal(node.right);
System.out.println(node.element);
}
层序遍历
- 访问顺序
- 从上到下,从左到有依次访问
- 使用队列实现
- 先将根节点入队
- 循环以下操作
- 将对头节点出队
- 将出队的节点的左子节点和右子节点入队
public void leverOderTraversal() {
if (null == root) return;
Queue<Node<E>> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
Node<E> node = queue.poll();
System.out.println(node.element);
if (null != node.left) {
queue.offer(node.left);
}
if (null != node.right) {
queue.offer(node.right);
}
}
}
删除节点(可查找相关文章查看如何实现,不想写了)
在删除节点之前需要了解的一些概念
前驱节点 : 某个节点按中序遍历后的前一个节点
后继节点 : 某个节点按中序遍历后的后一个节点
/**
* 查找某个节点的前驱节点
* 前驱节点:中序遍历的前一个节点
* @param node
* @return
*/
private Node<E> predecessor(Node<E> node) {
if (node == null) return null;
//前驱节点再左子树当中(left.right.right....)
Node<E> pre = node.left;
if (pre != null) {
while (pre.right != null) {
pre = pre.right;
}
return pre;
}
//从父节点、祖父节点中寻找前驱节点
while (node.parent != null && node == node.parent.left){
node = node.parent; //叶子节点在parent的左边
}
return node.parent;
}
/**
* 获取节点的后继节点
* 后继结点:中序遍历的后一个节点
* @param node
* @return
*/
private Node<E> sucessor(Node<E> node) {
if (node == null) return null;
// 前驱节点在左子树当中(right.left.left.left....)
Node<E> pre = node.right;
if (pre != null) {
while (pre.left != null) {
pre = pre.left;
}
return pre;
}
// 从父节点、祖父节点中寻找前驱节点
while (node.parent != null && node == node.parent.right) {
node = node.parent;
}
return node.parent;
}
删除节点
/**
* 根据元素查找节点
* @param element
* @return
*/
private Node<E> node(E element) {
Node<E> node = root;
while (node != null) {
int cmp = compare(element, node.element);
if (cmp == 0) return node;
if (cmp > 0) {
node = node.right;
}else {
node = node.left;
}
}
return null;
}
/**
* 删除节点
* @param element
*/
public void remove(E element) {
remove(node(element));
}
private void remove(Node<E> node){
if (node == null) return;
size--;
if (node.hasTwoDeg()) {
//度为2的节点
//找到后继节点
Node<E> s = sucessor(node);
//用后继节点覆盖度为2的节点的值
node.element = s.element;
//删除后继节点
node = s;
}
//删除node节点(度为1或者0)
Node<E> replace = node.left != null ? node.left : node.right;
if (replace != null) { // node是度为1的节点
// 更改parent
replace.parent = node.parent;
// 更改parent的left、right的指向
if (node.parent == null) { // node是度为1的节点并且是根节点
root = replace;
} else if (node == node.parent.left) {
node.parent.left = replace;
} else { // node == node.parent.right
node.parent.right = replace;
}
} else if (node.parent == null) { // node是叶子节点并且是根节点
root = null;
} else { // node是叶子节点,但不是根节点
if (node == node.parent.left) {
node.parent.left = null;
} else { // node == node.parent.right
node.parent.right = null;
}
}
}