持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第16天,点击查看活动详情
二叉搜索树
二叉搜索树(Binary Search Tree)是二叉树的一种,是应用非常广泛的一种二叉树,英文简称为BST
又被称为:二叉查找树、二叉排序树
任意一个节点的值都大于其左子树所有节点的值
任意一个节点的值都小于其右子树所有节点的值
它的左右子树也是一颗二叉搜索树
二叉搜索树可以大大提高搜索数据的效率
二叉搜索树存储的元素必须具备可比较性
二叉搜索树的接口设计
- int size() //元素的数量
- boolean isEmpty() //是否为空
- void clear() //清空所有元素
- void add(E element) //添加元素
- boolean contains(E element) //是否包含某元素
添加节点
添加步骤
- 找到父节点parent
- 创建新节点node
- parent.left = node 或者 parent.right = node
import printer.BinaryTreeInfo;
import java.util.Comparator;
@SuppressWarnings("unchecked")
public class BinarySearchTree<E> implements BinaryTreeInfo {
private int size;
private Node<E> root;
private Comparator<E> comparator;
public BinarySearchTree(){
this(null);
}public BinarySearchTree(Comparator<E> comparator){
this.comparator=comparator;
}
public int size(){
return size;
}
public boolean isEmpty(){
return size ==0;
}
public void clear(){
}
public void add(E element){
elementNotNullCheck(element);
// 添加第一个节点
if(root==null){
root=new Node<>(element,null);
size++;
return;
}
// 添加的不是第一个节点
// 找到父节点
Node<E> parent = root;
Node<E> node = root;
int cmp =0;
while(node!=null){
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;
}
}
// 看看插入到父节点的哪个位置
Node<E> newNode = new Node<>(element,parent);
if(cmp>0){
parent.right=newNode;
}else{
parent.left=newNode;
}
size++;
}
public void remove(E element){
}
public boolean contains(E element){
return node(element)!=null;
}
/**
*
* @return 返回值等于0,代表e1和e2相等;返回值大于0,代表e1大于e2;返回值小于0,代表e1小于e2;
*/
private int compare(E e1,E e2){
if(comparator!=null) {
return comparator.compare(e1, e2);
}
return ((Comparable<E>)e1).compareTo(e2);
}
private void elementNotNullCheck(E element){
if(element==null){
throw new IllegalArgumentException("element must not be null");
}
}
@Override
public Object root() {
return root;
}
@Override
public Object left(Object node) {
return ((Node<E>)node).left;
}
@Override
public Object right(Object node) {
return ((Node<E>)node).right;
}
@Override
public Object string(Object node) {
return ((Node<E>)node).element;
}
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;
}
}
}
二叉树的遍历
遍历是数据结构中的常见操作
把所有元素都访问一遍
-
根据节点访问顺序的不同,二叉树的常见遍历方式有4种
前序遍历(Preorder Traversal)
中序遍历(Inorder Traversal)
后序遍历(Postorder Traversal)
层序遍历(Level Order Traversal)
前序遍历
访问顺序
根节点、前序遍历左子树、前序遍历右子树
public void preorderTraversal(){
preorderTraversal(root);
}
private void preorderTraversal(Node<E> node){
if(node==null)return;
System.out.println(node.element);
preorderTraversal(node.left);
preorderTraversal(node.right);
}
中序遍历
访问顺序
中序遍历左子树、根节点、中序遍历右子树
二叉搜索数的中序遍历结果是升序或者降序的
public void inorderTraversal(){
inorderTraversal(root);
}
private void inorderTraversal(Node<E> node){
if(node==null)return;
inorderTraversal(node.left);
System.out.println(node.element);
inorderTraversal(node.right);
}
后序遍历
访问顺序
后序遍历左子树、后序遍历右子树、根节点
public void postorderTraversal(){
postorderTraversal(root);
}
private void postorderTraversal(Node<E> node){
if(node==null)return;
postorderTraversal(node.left);
postorderTraversal(node.right);
System.out.println(node.element);
}
层序遍历
访问顺序
从上到下、从左到右依次访问每一个节点
实现思路:使用队列
- 将根节点入队
- 循环执行以下操作,直到队列为空
- 将队头节点A出队,进行访问
- 将A的左子节点入队
- 将A的右子节点入队
public void levelOrderTraversal(){
if(root==null)return;
Queue<Node<E>> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()){
Node<E> node = queue.poll();
System.out.println(node.element);
if(node.left!=null){
queue.offer(node.left);
}
if(node.right!=null){
queue.offer(node.right);
}
}
}
设计遍历接口
public static interface Visitor<E>{
void visit(E element);
}
public void preorder(Visitor<E> visitor) {
preorder(root, visitor);
}
private void preorder(Node<E> node, Visitor<E> visitor) {
if (node == null || visitor.stop) return;
visitor.visit(node.element);
preorder(node.left, visitor);
preorder(node.right, visitor);
}
public void inorder(Visitor<E> visitor) {
inorder(root, visitor);
}
private void inorder(Node<E> node, Visitor<E> visitor) {
if (node == null || visitor.stop) return;
inorder(node.left, visitor);
visitor.visit(node.element);
inorder(node.right, visitor);
}
public void postorder(Visitor<E> visitor) {
postorder(root, visitor);
}
private void postorder(Node<E> node, Visitor<E> visitor) {
if (node == null || visitor.stop) return;
postorder(node.left, visitor);
postorder(node.right, visitor);
visitor.visit(node.element);
}
public void levelOrder(Visitor<E> visitor) {
if (root == null || visitor == null) return;
Queue<Node<E>> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
Node<E> node = queue.poll();
visitor.visit(node.element);
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
}
bst.preorder(new Visitor<Integer>() {
public void visit(Integer element) {
System.out.print(element + " ");
}
});
System.out.println();
bst.inorder(new Visitor<Integer>() {
public void visit(Integer element) {
System.out.print(element + " ");
}
});
bst.postorder(new Visitor<Integer>() {
public void visit(Integer element) {
System.out.print(element + " ");
}
});
bst.levelOrder(new Visitor<Integer>() {
public void visit(Integer element) {
System.out.print(element + " ");
}
});
增强遍历接口
public void preorder(Visitor<E> visitor) {
if (visitor == null) return;
preorder(root, visitor);
}
private void preorder(Node<E> node, Visitor<E> visitor) {
if (node == null || visitor.stop) return;
visitor.stop = visitor.visit(node.element);
preorder(node.left, visitor);
preorder(node.right, visitor);
}
public void inorder(Visitor<E> visitor) {
if (visitor == null) return;
inorder(root, visitor);
}
private void inorder(Node<E> node, Visitor<E> visitor) {
if (node == null || visitor.stop) return;
inorder(node.left, visitor);
if (visitor.stop) return;
visitor.stop = visitor.visit(node.element);
inorder(node.right, visitor);
}
public void postorder(Visitor<E> visitor) {
if (visitor == null) return;
postorder(root, visitor);
}
private void postorder(Node<E> node, Visitor<E> visitor) {
if (node == null || visitor.stop) return;
postorder(node.left, visitor);
postorder(node.right, visitor);
if (visitor.stop) return;
visitor.stop = visitor.visit(node.element);
}
public void levelOrder(Visitor<E> visitor) {
if (root == null || visitor == null) return;
Queue<Node<E>> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
Node<E> node = queue.poll();
if (visitor.visit(node.element)) return;
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
}
static void test9() {
Integer data[] = new Integer[] {
7, 4, 9, 2, 1
};
BinarySearchTree<Integer> bst = new BinarySearchTree<>();
for (int i = 0; i < data.length; i++) {
bst.add(data[i]);
}
BinaryTrees.println(bst);
bst.preorder(new Visitor<Integer>() {
public boolean visit(Integer element) {
System.out.print(element + " ");
return element == 2 ? true : false;
}
});
System.out.println();
bst.inorder(new Visitor<Integer>() {
public boolean visit(Integer element) {
System.out.print(element + " ");
return element == 4 ? true : false;
}
});
System.out.println();
bst.postorder(new Visitor<Integer>() {
public boolean visit(Integer element) {
System.out.print(element + " ");
return element == 4 ? true : false;
}
});
System.out.println();
bst.levelOrder(new Visitor<Integer>() {
public boolean visit(Integer element) {
System.out.print(element + " ");
return element == 9 ? true : false;
}
});
System.out.println();
}
计算二叉树的高度
private int height(Node<E> node){
if(node==null) return 0;
return 1 + Math.max(height(node.left),height(node.right));
}
public int height() {
if (root == null) return 0;
// 树的高度
int height = 0;
// 存储着每一层的元素数量
int levelSize = 1;
Queue<Node<E>> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
Node<E> node = queue.poll();
levelSize--;
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
if (levelSize == 0) { // 意味着即将要访问下一层
levelSize = queue.size();
height++;
}
}
return height;
}
判断一棵树是否为完全二叉树
-
如果树为空,返回false
-
如果树不为空,开始层序遍历二叉树(用队列)
如果node.left!=null&&node.right!=null,将node.left、node.right按顺序入队
如果node.left==null&&node.right!=null,返回false
如果node.left!=null&&node.right==null或者node.left==null&&node.right==null那么后面遍历的节点应该都为叶子节点,才是完全二叉树,否则返回false
public boolean isComplete(){
if(root==null) return false;
Queue<Node <E>> queue = new LinkedList<>();
queue.offer(root);
boolean leaf=false;
while(!queue.isEmpty()){
Node<E> node = queue.poll();
if(leaf&&!node.isLeaf()) return false;
if(node.left!=null&&node.right!=null){
queue.offer(node.left);
queue.offer(node.right);
}else if(node.left==null&&node.right!=null){
return false;
}else{
leaf=true;
if(node.left!=null){
queue.offer(node.left);
}
}
}
return true;
}
public boolean isComplete(){
if(root==null) return false;
Queue<Node<E>> queue = new LinkedList<>();
queue.offer(root);
boolean leaf = false;
while(!queue.isEmpty()){
Node<E> node = queue.poll();
if(leaf&&!node.isLeaf()) return false;
if(node.left!=null){
queue.offer(node.left);
}else if(node.right!=null){
//node.left==null&&node.right!=null
return false;
}
if(node.right!=null){
queue.offer(node.right);
}else{
//node.right==null
leaf=true;
}
}
return true;
}
重构二叉树
以下结果可以保证重构出唯一的一颗二叉树
- 前序遍历+中序遍历
- 后序遍历+中序遍历
- 前序遍历+后序遍历
- 如果它是一颗真二叉树,结果是唯一的
- 否则结果不唯一
前序遍历+中序遍历重构二叉树
- 前序遍历:4 2 1 3 6 5
- 中序遍历:1 2 3 4 5 6
根 左 右
左 根 右
- 前序:先得出4为根节点
- 中序:得出 1 2 3 在左边 5 6 在右边
- 前序:得出 2 6 为第二层的节点
- 综合得出 最后结果
后序遍历+中序遍历重构二叉树
- 后序遍历 1 3 2 5 6 4
- 中序遍历 1 2 3 4 5 6
左 右 根
左 根 右
- 后序:得出 4为根节点
- 中序:得出 1 2 3 为左子树 5 6 为右子树元素
- 后序:得出2 6为根节点
- 综合得出 最后结果
前驱节点
- 中序遍历时的前一个节点
- 如果是二叉搜索树,前驱节点就是前一个比它小的节点
- node.left !=null
- predecessor = node.left.right.right.right...
- 终止条件:right为null
- node.left == null && node.parent != null
- predecessor = node.parent.parent.parent...
- 终止条件:node在parent的右子树中
- node.left == null && node.parent == null
- 那就没有前驱节点
private Node<E> predecessor(Node<E> node){
if(node==null) return null;
//前驱节点在左子树当中
Node<E> p = node.left;
if(p!=null){
while(p.right!=null){
p=p.right;
}
return p;
}
while(node.parent!=null&&node==node.parent.left){
node=node.parent;
}
return node.parent;
}
后继节点
- 中序遍历时的后一个节点
- 如果是二叉搜索树,后继节点就是后一个比它大的节点
- node.right!=null
- successor=node.right.left.left.left...
- 终止条件:left为null
- node.right == null && node.parent !=null
- successor=node.parent.parent.parent...
- 终止条件:node在parent的左子树中
- node.right == null && node.parent == null
- 那就没有后继节点
private Node<E> successor(Node<E> node){
if(node==null) return null;
//前驱节点在左子树当中
Node<E> p = node.right;
if(p!=null){
while(p.left!=null){
p=p.left;
}
return p;
}
while(node.parent!=null&&node==node.parent.right){
node=node.parent;
}
return node.parent;
}
remove
删除节点-叶子节点
-
直接删除
-
node == node.parent.left
- node.parent.left=null
-
node == node.parent.right
- node.parent.right=null
-
node.parent == null
- root = null
删除节点-度为1的节点
- 用子节点替代原节点的位置
- child 是 node.left 或者 child 是 node.right
- 用child替代node的位置
- 如果node是左子节点
- child.parent=node.parent
- node.parent.left=child
- 如果node是右子节点
- child.parent = node.parent
- node.parent.right = child
- 如果node是根节点
- root=child
- child.parent=null
删除节点-度为2的节点
- 先用前驱或者后继节点的值覆盖原节点的值
- 然后删除相应的前驱或者后继节点
- 如果一个节点的度为2,那么它的前驱、后继节点的度只可能是1和0
代码实现
public void remove(E element){
remove(node(element));
}
public boolean contains(E element){
return node(element)!=null;
}
private void remove(Node<E> node){
if(node==null) return;
size--;
if(node.hasTwoChildren()){//度为2的节点
//找到后继节点
Node<E> s = successor(node);
//用后继节点的值覆盖度为2的节点的值
node.element=s.element;
//删除后继节点
node=s;
}
//删除node节点(node的度必然是1或者0)
Node<E> replacement = node.left!=null? node.left : node.right;
if(replacement!=null){//node是度为1的节点
//更改parent
replacement.parent = node.parent;
//更改parent的left、right的指向
if(node.parent==null){//node是度为1的节点并且是根节点
root=replacement;
}else if(node==node.parent.left){
node.parent.left=replacement;
}else if(node==node.parent.right){
node.parent.right=replacement;
}
}else if(node.parent==null){//node是叶子节点并且是根节点
root=null;
}else{//node是叶子节点,但不是根节点
if(node==node.parent.right){
node.parent.right=null;
}else{//node==node.parent.left
node.parent.left=null;
}
}
}
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;
}