特点
-
动态数据结构
-
是一颗二叉树
-
二分搜索树的每个节点的值:
- 每个节点的值都大于其左子树的所有节点的值
- 每个节点的值都小于其右子树的所有节点的值
-
每一颗子树也是二分搜索树
-
存储的元素必须有可比较性, Java中的话就要求二分搜索树保存的数据类型要实现Comparable接口, 或者使用额外的比较器实现
-
一般二分搜索树不包含重复元素, 当然也可以定义包含重复元素的二分搜索树
如果想要包含重复元素的话, 只需要定义二分搜索树的左节点的值小于等于当前节点的值或右节点的值大于等于当前节点即可
-
二分搜索树天然的具有递归特性
下面是二分搜索树的几个样例 ?*
操作
在进行相关操作之前, 先定义一个支持泛型的节点类, 用于存储二分搜索树每个节点的信息, 这个类作为二分搜索树的一个内部类, 二分搜索树的类声明以及Node节点类声明如下:
/**
* 递归实现二分搜索树
* 这里设计的树是不存储重复元素的, 重复添加元素只保存一个
* @author 七夜雪
*
*/
public class BSTree<E extends Comparable<E>> {
// 根节点
private Node root ;
// 树容量
private int size ;
public BSTree() {
this.root = null ;
this.size = 0 ;
}
public boolean isEmpty() {
return size == 0 ;
}
public int getSize(){
return size;
}
/**
* 二分搜索树节点类
* @author 七夜雪
*
*/
private class Node {
public E e ;
// 左右子树
public Node left , right ;
public Node(E e) {
this.e = e ;
this.left = null ;
this.right = null ;
}
}
}
添加元素
由于二分搜索树本身的递归特性, 所以可以很方便的使用递归实现向二分搜索树中添加元素, 步骤如下:
-
定义一个公共的add方法, 用于添加元素
-
定义一个递归的add方法用于实际向二分搜索树中添加元素
对于这个递归方法, 设定的方法语义为,向以node为根节点的树上添加一个元素, 并返回插入新节点后的二分搜索树的根节点
具体代码实现如下,:
// 类声明 : public class BSTree<E extends Comparable<E>>
/**
* 向二分搜索树上添加节点,
* @param e
*/
public void add(E e) {
root = add(root, e) ;
}
/**
* 向以node节点为根节点的树上添加元素E
* 递归方法
* @param node
* @param e
* @return 返回插入新节点后的二分搜索树的根节点
*/
private Node add(Node node, E e) {
/*
* 递归终止条件, node为null, 表示查找到要添加的节点了
if (node == null) {
size++ ;
return new Node(e) ;
}
// 添加的元素小于当前元素, 向左递归
if (node.e.compareTo(e) > 0) {
/*
* 由于递归的add方法的语义是添加新元素, 并返回新的二分搜索树的根节点
* 所以这里需要使用node.left = add(node.left, e), 接收递归方法返回的值
*/
node.left = add(node.left, e) ;
// 添加的元素小于当前元素, 向右递归
} else if (node.e.compareTo(e) < 0) {
// 和向左递归一个道理
node.right = add(node.right, e) ;
}
// 由于定义的二分搜索树不保存重复元素, 所以针对node.e.compareTo(e) == 0的这种情况这里不做任何处理
return node ;
}
查找元素
由于二分搜索树没有下标, 所以针对二分搜索树的查找操作, 这里定义一个contains方法, 查看二分搜索树是否包含某个元素, 返回一个布尔型变量, 这个查找的操作一样是一个递归的过程, 具体代码实现如下:
/**
* 判断树中是否包含元素e
* @param e
* @return
*/
public boolean contains(E e) {
return contains(root, e) ;
}
/**
* 判断以node为根节点的二分搜索树中是否包含元素e
* 递归方法
* @param node
* @param e
* @return
*/
private boolean contains(Node node, E e) {
// 递归终结条件, 当node等于null时, 表示已经查询到根节点了, 但是没有找到对应的元素E
if (node == null) {
return false ;
}
// 递归终结条件
if (node.e.compareTo(e) == 0) {
// node.e == e
return true;
// 根据二分搜索树特性, e小于当前节点的值时, 向左递归
} else if (node.e.compareTo(e) > 0) {
// node.e > e
return contains(node.left, e) ;
// 根据二分搜索树特性, e大于当前节点的值时, 向右递归
} else {
// node.e < e
return contains(node.right, e) ;
}
}
遍历操作
-
遍历操作就是把所有的节点都访问一遍
-
访问的原因和业务相关
-
遍历分类
深度优先遍历 :
- 前序遍历 : 对当前节点的遍历在对左右孩子节点的遍历之前, 遍历顺序 : 当前节点->左孩子->右孩子
- 中序遍历 : 对当前节点的遍历在对左右孩子节点的遍历中间, 遍历顺序 : 左孩子->当前节点->右孩子
- 后序遍历 : 对当前节点的遍历在对左右孩子节点的遍历之后, 遍历顺序 : 左孩子->右孩子->当前节点
广度优先遍历 :
- 层序遍历 : 按层从左到右进行遍历
前序遍历
- 最自然的遍历方式
- 最常用的遍历方式
这里一样使用递归来实现遍历, 对于一颗二分搜索树进行遍历, 如果要使用非递归方式实现的话, 可以使用一个栈来赋值进行遍历, 代码如下:
/**
* 前序遍历树
*/
public void preOrder() {
preOrder(root) ;
}
/**
*
* 前序遍历的递归方法, 深度优先
* 前序遍历是指,先访问当前节点, 然后再访问左右子节点
* @param node
*/
private void preOrder(Node node) {
// 递归终止条件
if (node == null) {
return ;
}
// 1. 前序遍历先访问当前节点
System.out.println(node.e) ;
// 2. 前序遍历访问左孩子
preOrder(node.left) ;
// 3. 前序遍历访问右孩子
preOrder(node.right) ;
}
非递归写法 :
/**
* 前序遍历的非递归方法, 深度优先
* 这里使用栈进行辅助实现
* 前序遍历是指,先访问当前节点, 然后再访问左右子节点
*/
public void preOrderNr() {
// 使用栈辅助实现前序遍历
Stack<Node> stack = new Stack<>();
/*
* 前序遍历的过程就是先访问当前节点, 然后再访问左子树, 然后右子树
* 所以使用栈实现时, 可以先将当前节点入栈, 当前节点出栈时,
* 分别将当前节点的右孩子, 左孩子压入栈
*/
// 首先将根节点压入栈
stack.push(root);
while (!stack.isEmpty()) {
Node cur = stack.pop();
// 前序遍历当前节点
System.out.println(cur.e) ;
// 由于栈是后入先出, 前序遍历是先左孩子, 再右孩子, 所以这里需要先将右孩子压入栈
if (cur.right != null) {
stack.push(cur.right);
}
if (cur.left != null) {
stack.push(cur.left);
}
}
}
中序遍历
- 二分搜索树的中序遍历的结果是顺序的
/**
* 中序遍历树, 深度优先
*/
public void inOrder() {
inOrder(root) ;
}
/**
*
* 中序遍历的递归方法, 深度优先
* 中序遍历指的是访问当前元素的顺序放在访问左右子节点之间
* 中序遍历的结果是有序的
* @param node
*/
private void inOrder(Node node) {
//