一、二分搜索树(Binary Search Tree)
对于树结构,我们大家所熟知的就是二分搜索树了吧。二分搜索树也是我们面试中最常被问到的树结构。
首先,二分搜索树有什么特点呢?
- 是动态数据的结构
- 是一颗二叉树
- 每个节点的值都大于其左子树的所有节点的值;每个节点的值都小于其右子树的所有节点的值
- 每一颗子树也是二分搜索树
- 存储的元素必须有可比较性, Java中的话就要求二分搜索树保存的数据类型要实现Comparable接口, 或者使用额外的比较器实现
- 二分搜索树天然的具有递归特性
下面实现了一个基础的二分搜索树:(运用递归思想)
//定义泛型并且需要继承Comparable接口,使其具有可比较性
public class BST<E extends Comparable<E>> {
private class Node {
private E e;
public Node left, right;
public Node(E e) {
this.e = e ;
left = null ;
right = null;
}
}
private Node root;
private int size;
public BST(){
root = null ;
size = 0;
}
public int size(){
return size;
}
public boolean isEmpty(){
return size == 0;
}
public void add(E e) {
// if (root == null) {
// root = new Node(e);
// size++;
// }
// else
// add(root,e);
//使用递归的方法调用add方法
root = add(root, e);
}
private Node add(Node node, E e) {
if (node == null) {
//当查到叶子节点的时候,新建一个值为e的节点
size++;
return new Node(e);
}
//比较新增节点与当前节点的大小
//大的对当前节点的左子树进行递归
//小的对当前节点的右子树进行递归
if (e.compareTo(node.e) < 0) {
node.left = add(node.left,e);
} else if (e.compareTo(node.e) > 0) {
node.right = add(node.right,e);
}
return node;
}
// if (e.equals(node.e))
// return;
// else if (e.compareTo(node.e) < 0 && node.left == null) {
// node.left = new Node(e);
// size ++;
// return;
// } else if (e.compareTo(node.e) > 0 && node.right == null) {
// node.right = new Node(e);
// size++;
// return;
// }
// if (e.compareTo(node.e)<0)
// add(node.left,e);
// else
// add(node.right,e);
// }
//递归查找是否包含该节点
public boolean contains(E e) {
return contains(root,e);
}
private boolean contains(Node node ,E e) {
if (node == null) {
return false;
}
if (e.compareTo(node.e) == 0) {
return true;
} else if (e.compareTo(node.e) < 0) {
return contains(node.left,e);
}else {
return contains(node.right,e);
}
}
}
二分搜索树的遍历
遍历分类:
深度优先遍历 :
前序遍历 : 对当前节点的遍历在对左右孩子节点的遍历之前, 遍历顺序 : 当前节点->左孩子->右孩子
中序遍历 : 对当前节点的遍历在对左右孩子节点的遍历中间, 遍历顺序 : 左孩子->当前节点->右孩子
后序遍历 : 对当前节点的遍历在对左右孩子节点的遍历之后, 遍历顺序 : 左孩子->右孩子->当前节点
广度优先遍历 :
层序遍历 : 按层从左到右进行遍历
代码实现:
//前序遍历
public void preOrder(){
preOrder(root);
}
private void preOrder(Node node) {
if (node == null) {
return;
}
System.out.print(node.e+" ");
preOrder(node.left);
preOrder(node.right);
}
//前序遍历的非递归实现
private void preOrder1(Node node){
// 使用栈辅助实现前序遍历
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);
}
private void inOrder(Node node){
if (node == null) {
return;
}
inOrder(node.left);
System.out.print(node.e+" ");
inOrder(node.right);
}
//后序遍历
public void aftOrder(){
aftOrder(root);
}
private void aftOrder(Node node) {
if (node == null) {
return;
}
aftOrder(node.left);
aftOrder(node.right);
System.out.print(node.e+" ");
}
//二分搜索树的层序遍历
public void levelOrder() {
//利用队列进行层序遍历,先进先出
Queue<Node> q = new LinkedList<>();
q.add(root);
//每遍历一个节点,就将该节点的左右子树(如果存在左右子树)入队
while(!q.isEmpty()){
Node cur = q.remove();
System.out.print(cur.e+" ");
if (cur.left!=null)
q.add(cur.left);
if (cur.right != null)
q.add(cur.right);
}
}
二分搜索树的删除操作:
删除二分搜索树中的最大值和最小值:
//通过递归找出最小值
public E minMun(){
if (size == 0 )
throw new IllegalArgumentException("BST is null");
return minMun(root).e;
}
private Node minMun(Node node){
if (node.left == null)
return node;
return minMun(node.left);
}
//删除二分搜索树中最小的值的节点,并返回最小值
public E removeMin(){
//保存最小节点
Node e = minMun(root);
root = removeMin(root);
return e.e;
}
private Node removeMin(Node node){
//判断当前节点是不是最小的节点
if (node.left == null) {
//删除当前节点操作
Node rightNode = node.right;
node.right = null;
size -- ;
return rightNode;
}
//如果不是最小节点,通过递归找出最小节点
node.left = removeMin(node.left);
return node;
}
//找出最大节点
public E maxMun(){
if (size == 0 )
throw new IllegalArgumentException("BST is null");
return maxMun(root).e;
}
private Node maxMun(Node node){
if (node.right == null)
return node;
return maxMun(node.right);
}
//删除二分搜索树中最大的值的节点,并返回最大值
public E removeMax(){
Node e = maxMun(root);
root = removeMax(root);
return e.e;
}
private Node removeMax(Node node){
if (node.right == null) {
Node leftNode = node.left;
node.left = null