二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。
二叉搜索树对节点所包含的数据进行了一些约束。使得二叉搜索树最坏情况下的平均搜索时间复杂度降低至logn 这是我们使用二叉搜索树的重要原因之一
二叉搜索树的性质
1.一个节点的左子树只能包含小于该节点键值得节点
2.一个节点的右子树只能包含大于该节点键值得节点
3.左子树和右子树也必须是二叉搜索树
了解了这么多二叉搜索树的性质 我们开始实现它吧
树的节点类
//树的节点
public class TreeNode<T> {
private T data;
public TreeNode<T> leftNode;
public TreeNode<T> rightNode;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
二叉搜索树的基础功能实现
//二叉树基础功能
//节点的左子节点的值都小于这个节点的值,节点的右子节点的值都大于等于这个节点的值
//参考书籍<<数据库结构与算法分析>> mark allen weiss
public abstract class BinaryTree<T extends Comparable<? super T>> {
// 定义根节点
private TreeNode<T> root;
// 初始化二叉树
public BinaryTree() {
root = null;
}
// 删除二叉树
public void delete() {
root = null;
}
// 判断二叉树是否为空
public boolean isEmpty() {
return root == null;
}
// 判断节点是否在此树中
public boolean contain(T x) {
return contain(x, root);
}
private boolean contain(T x, TreeNode<T> root) {
// 空树
if (root == null)
return false;
// 节点的左子节点的值都小于这个节点的值,节点的右子节点的值都大于等于这个节点的值
int compareResult = x.compareTo(root.getData());
// 若被查找值比节点小 则访问左节点 反之访问右节点
if (compareResult > 0) {
contain(x, root.rightNode);
} else if (compareResult < 0)
contain(x, root.leftNode);
return true;
}
// 查找树中最大的节点
public TreeNode<T> findMax() throws Exception {
if (isEmpty())
throw new Exception("空树");
return findMax(root);
}
private TreeNode<T> findMax(TreeNode<T> root) throws Exception {
// 判断是否为空树
if (root == null)
return null;
// 递归出口
else if (null == root.rightNode)
return root;
// 节点的右子节点的值都大于等于这个节点的值
return findMax(root.rightNode);
}
// 查找树中最小的数节点
public TreeNode<T> findMin() throws Exception {
if (isEmpty())
throw new Exception("空树");
return findMin(root);
}
private TreeNode<T> findMin(TreeNode<T> root2) {
// 判断是否为空树
if (root == null)
return null;
// 递归出口
else if (null == root.leftNode)
return root;
// 节点的左子节点的值都小于这个节点的值
return findMin(root.leftNode);
}
// 插入数据
public void insert(T x) {
root = insert(x, root);
}
private TreeNode<T> insert(T x, TreeNode<T> root) {
// 递归出口
if (null == root) {
root = new TreeNode<>();
root.setData(x);
return root;
}
int compareResult = x.compareTo(root.getData());
// 节点的左子节点的值都小于这个节点的值
if (compareResult < 0) {
return insert(x, root.leftNode);
}
// 节点的右子节点的值都大于等于这个节点的值
return insert(x, root.rightNode);
}
// 删除数据
public void remove(T x) {
root = remove(x, root);
}
private TreeNode<T> remove(T x, TreeNode<T> root) {
// 不存在x
if (null == root)
return root;
int compareResult = x.compareTo(root.getData());
if (compareResult < 0)
remove(x, root.leftNode);
else if (compareResult > 0)
remove(x, root.rightNode);
/*
* 找到了删除的节点但是他有两个子节点 一般的策略是将其右节点的最小值替换这个节点
*/
else if (root.leftNode != null && root.rightNode != null) {
// 将右节点的最小值替换当前节点
root.setData(findMin(root.rightNode).getData());
// 删除右边最小值
root.rightNode = remove(root.getData(), root.rightNode);
} else
// 只有一个子节点的节点
root = (root.leftNode == null) ? root.rightNode : root.leftNode;
return root;
}
// 遍历二叉树
// 前序遍历
public abstract void preOrder(TreeNode<T> root);
// 中序遍历
public abstract void inOrder(TreeNode<T> root);
// 后序遍历
public abstract void postOrder(TreeNode<T> root);
}
二叉树的遍历分为递归方法和非递归法
二叉搜索树递归遍历
//递归遍历二叉树
public class NonRecursiveTraversalBinaryTree<T extends Comparable<? super T>> extends BinaryTree<T> {
/*
* 前序遍历
* 1.访问根节点
* 2.按照前序遍历的方式遍历左子树
* 3.按照前序遍历的方式遍历右字树
* @see com.tree.binarytree.BinaryTree#preOrder(com.tree.binarytree.TreeNode)
*/
@Override
public void preOrder(TreeNode<T> root) {
if (null != root) {
//访问根节点
System.out.println(root.getData());
//访问左子树
preOrder(root.leftNode);
//访问右子树
preOrder(root.rightNode);
}
}
/*
*中序遍历
*1.按照中序遍历的方式遍历左子树
*2.访问根节点
*3.按照中序遍历的方式遍历右子树
* @see com.tree.binarytree.BinaryTree#inOrder(com.tree.binarytree.TreeNode)
*/
@Override
public void inOrder(TreeNode<T> root) {
inOrder(root.leftNode);
System.out.println(root.getData());
inOrder(root.rightNode);
}
/*
* 后序遍历
* 1.按照后序遍历的方式遍历左子树
* 2.按照后序遍历的方式遍历右子树
* 3.访问根节点
* @see com.tree.binarytree.BinaryTree#postOrder(com.tree.binarytree.TreeNode)
*/
@Override
public void postOrder(TreeNode<T> root) {
postOrder(root.leftNode);
postOrder(root.rightNode);
System.out.println(root.getData());
}
}
非递归遍历二叉搜索树
//非递归遍历二叉树
public class RecursiveTraversalBinaryTree<T extends Comparable<? super T>> extends BinaryTree<T> {
/*
* 非递归的前序遍历 在递归方法中需要采用一个栈来记录当前的节点以便完成了左子树后可以返回到右子树
*
* @see
* com.tree.binarytree.BinaryTree#preOrder(com.tree.binarytree.TreeNode)
*/
@Override
public void preOrder(TreeNode<T> root) {
if (root != null) {
// 创建栈
Stack<TreeNode<T>> stack = new Stack<>();
// 利用无限循环来模拟递归
while (true) {
while (root != null) {
// 访问根节点
System.out.println(root.getData());
stack.push(root);
// 访问左节点
root = root.leftNode;
}
// 如果栈为空退出
if (stack.isEmpty())
break;
// 弹栈来依次访问右节点
root = stack.pop();
root = root.rightNode;
}
}
}
@Override
public void inOrder(TreeNode<T> root) {
if (root != null) {
// 创建栈
Stack<TreeNode<T>> stack = new Stack<>();
// 利用无限循环来模拟递归
while (true) {
while (root != null) {
stack.push(root);
// 访问左节点
root = root.leftNode;
}
// 如果栈为空退出
if (stack.isEmpty())
break;
// 弹栈来依次访问右节点
root = stack.pop();
// 访问根节点
System.out.println(root.getData());
root = root.rightNode;
}
}
}
/*
* 前序和后序的遍历 当元素出栈后就不需要再次访问,但是在后序遍历中每个节点要访问两次 这就意味着 在遍历完左子树后需要访问当前节点
* 之后遍历完右子树后还得访问该当前节点 解决方法 当从栈中出栈一个元素时 检查这个元素与栈顶元素的右子节点是否相同 若相同 则完成了左右树的遍历
* 此时只需将栈顶元素出栈一次并输出该节点数据即可
*
* @see
* com.tree.binarytree.BinaryTree#postOrder(com.tree.binarytree.TreeNode)
*/
@Override
public void postOrder(TreeNode<T> root) {
if (root != null) {
// 创建栈
Stack<TreeNode<T>> stack = new Stack<>();
// 利用无限循环来模拟递归
while (true) {
if (root != null) {
stack.push(root);
// 访问左节点
root = root.leftNode;
} else {
// 如果栈为空退出
if (stack.isEmpty())
return;
// 如果该节点的右节点为null 则已到底 否则还有右节点没有访问
if (stack.peek().rightNode == null) {
// 则弹栈
root = stack.pop();
// 访问节点
System.out.println(root.getData());
// 如果该节点是父节点的右节点则证明其父节点的左右子树都访问完成
if (root == stack.peek().rightNode) {
root = stack.pop();
System.out.println(root.getData());
}
}
// 栈不为空则访问其右节点
if (!stack.isEmpty())
root = stack.peek().rightNode;
}
}
}
}
/*
* 层次遍历 层次遍历定义 1.访问根节点 2.在访问 i层时将i+1层的结点按照顺序保存在队列中 3.进入下一层并访问所有节点
* 4.重复上述操作直至所有层访问完
*/
public void LevelOrder(TreeNode<T> root) {
// 创建队列
Queue<TreeNode<T>> queue = new ArrayDeque<>();
if (root == null)
return;
// 利用队列先进先出的原则来实现层次遍历
queue.add(root);
while (!queue.isEmpty()) {
//弹出队列首节点
TreeNode<T> node = queue.remove();
System.out.println(node.getData());
//将节点的左右子节点加入队列
if(node.leftNode != null)
queue.add(node.leftNode);
if(node.rightNode != null)
queue.add(node.rightNode);
}
}
至此二叉搜索树的功能基本完成