------------二分搜索树-------------
AVL和红黑树都属于平衡二叉树。
二叉树和链表一样,也属于动态数据结构(不需要事先定义容量)
class Node{
E e;
Node left;
Node right;
}
二叉树具有递归结构:每个节点的左子树是二叉树,每个节点的右子树也是二叉树。
满二叉树定义:除了叶子节点,每个节点都有两个孩子。
只有一个节点也可以看成是二叉树,只要满足二叉树的定义
二分搜索树:本身是二叉树,同时必须满足两个条件:1)节点的值大于其左子树的所有节点的值;2)节点的值小于其右子树的所有节点的值。相当于它的每一颗子树也都是二分搜索树。
以上描述的二叉树或者二叉搜索树都不一定是平衡的,即只有左子树或者只有右子树也可能是二分搜索树。
如果二叉树中存放自定义数据,要定义好两个自定义数据之间是如何进行大小比较的,这样二分树中才能查找数据。
1) 二分搜索树中添加元素
2) 二分搜索树中搜索元素-前序遍历、中序遍历和后序遍历(均是递归调用)(前中后序遍历都属于深度优先遍历)
3) 使用栈实现非递归调用的前序遍历
4) 层序遍历(层序遍历属于广度优先遍历):不是用递归,是借助队列实现,广度优先遍历的意义是更快的找到问题的解
5) 二分搜索树删除节点
具体代码如下:
package tree;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
/*
* 这是定义的二分搜索树,树中存放的数据必须明确声明如何比较大小
* 树中存放的数据E必须继承Comparable接口,这样才能保证E具有可比性
* 如下实现的二分搜索树是不包含重复元素的,也可尝试自行实现可包含重复元素的二分搜索树
* */
public class BST
//定义匿名内部类
private class Node{
public E e;
public Node left;
public Node right;
public Node(E e){
this.e = e;
left = null;
right = null;
}
}
//定义两个成员变量root和size
private Node root;
private int size;
//定义二分搜索树的构造方法
public BST(){
root = null;
size = 0;
}
public int getSize(){
return size;
}
public boolean isEmpty(){
return size == 0;
}
/*//向二分搜索树中增加新的元素e
public void add(E e){
if(root == null){
root = new Node(e);
size++;
}else{
add(root,e);
}
}
//如果e小于node中的e,递归左子树,如果e大于node中的e,递归右子树,直到比较的node是根节点,不需要断开两个节点直接的链接
private void add(Node node,E e){
//终止条件
if(e.compareTo(node.e) == 0){
//如果相等,不做处理,类的头部已经说明本二叉搜索树不包含重复数据
return;
}else if(e.compareTo(node.e) < 0 && node.left == null){
//如果小于node.e并且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){
//如果小于node.e并且node的left节点不为null
add(node.left,e);
//最终终止条件一会被执行到,终止条件中进行了size++,所以此处不要再进行size++,否则就加多了
}else{
add(node.right,e);
}
}*/
//优化后的,向二分搜索中加新的元素饿
public void add(E e){
root = add(root,e);
}
//优化后的,返回插入新节点后二分搜索树的根
private Node add(Node node,E e){
//如果首次调用,相当于创建根节点
//如果不是首次调用,相当于是创建叶子节点,拼接在父节点下
//终结条件
if(node == null){
size++;
return new Node(e);
}
//如果传进来的node不为null,则将e添加到node的左子树中
if(e.compareTo(node.e) < 0){
//如果node.left为null,相当于将e新new的node做为node.left
//如果node。left不为null,相当于将e新new的node递归加入node。left对应的左子树中
//注意,左节点为null是做为左节点,不为null,是加入到左子树中,是左节点和左子树的区别
//最后一次调用add(node.left,e),一定是node.left为null,这样才是终结条件
node.left = add(node.left,e);
}else if(e.compareTo(node.e) > 0){
node.right = add(node.right,e);
}
//最终返回node
return node;
}
//前序遍历(先遍历根节点),用户使用
public void preOrder(){
preOrder(root);
}
//私有的前序遍历,递归使用
private void preOrder(Node node){
//终止条件
//整个链表为null,或者子节点为null
if(node == null){
return;
}
//输出当前节点
System.out.println(node.e);
//递归调用
//遍历左子树右子树
preOrder(node.left);
preOrder(node.right);
}
@Override
//前序遍历生成toString()的字符串
public String toString() {
StringBuilder sb = new StringBuilder();
generateBSTString(root,0,sb);
return sb.toString();
}
//前序遍历,先访问该节点,再遍历左子树,再遍历右子树
private void generateBSTString(Node node,int depth,StringBuilder sb){
//终止条件
if(node == null){
return;
}
//中间这部分是逻辑处理
for(int i= 0;i
}
sb.append(node.e);
sb.append("\n");
//递归调用
generateBSTString(node.left,depth+1,sb);
generateBSTString(node.right,depth+1,sb);
}
//中序遍历:先遍历左子树,再遍历该节点,再遍历右子树
//中序遍历的输出结果是从小到的
public void middleOrder(){
middleOrder(root);
}
//私有的中序遍历,递归使用
private void middleOrder(Node node){
//终止条件
//整个链表为null,或者子节点为null
if(node == null){
return;
}
//递归调用
//遍历左子树
middleOrder(node.left);
//输出当前节点
System.out.println(node.e);
//遍历右子树
middleOrder(node.right);
}
//后续遍历:先访问左子树、再访问右子树、最后访问当前节点
//后续遍历的典型应用是为二分搜索树释放内存,因为必须处理完左右子节点,才能处理当前节点
//又或者是分支算法,都是因为需要先处理完两个子节点,再根据子节点的结果来处理当前节点
public void postOrder(){
postOrder(root);
}
//私有的中序遍历,递归使用
private void postOrder(Node node){
//终止条件
//整个链表为null,或者子节点为null
if(node == null){
return;
}
//递归调用
//遍历左子树
postOrder(node.left);
//遍历右子树
postOrder(node.right);
//输出当前节点
System.out.println(node.e);
}
//使用栈以非递归的形式前序遍历
public void preOrderUserStack(){
Stack
stack.push(root);
while(!stack.isEmpty()){
Node node = stack.pop();
System.out.println(node.e);
//由于用的是栈,需要右子树先入栈,左子树后入栈,这样才能左子树先出栈
if(node.right != null){
stack.push(node.right);
}
if(node.left != null){
stack.push(node.left);
}
}
}
//层序遍历,借助队列:先进先出
public void levelOrder(){
Queue
queue.add(root);
while(!queue.isEmpty()){
Node current = queue.poll();
System.out.println(current.e);
if(current.left != null){
queue.add(current.left);
}
if(current.right != null){
queue.add(current.right);
}
}
}
//寻找二分搜索树中的最小元素
public E minimum(){
if(size == 0){
throw new IllegalArgumentException("BST is empty.");
}
//最小元素
Node minumum = minumum(root);
return minumum.e;
}
//私有的内部递归调用的的方法
private Node minumum(Node node){
//终止条件
if(node.left == null){
return node;
}
return minumum(node.left);
}
//寻找二分搜索树中的最大元素
public E maximum(){
if(size == 0){
throw new IllegalArgumentException("BST is empty.");
}
//最小元素
Node maximum = maximum(root);
return maximum.e;
}
//私有的内部递归调用的的方法
private Node maximum(Node node){
//终止条件
if(node.right == null){
return node;
}
return maximum(node.right);
}
//删除二分搜索树中的最小元素
public E removeMin(){
E min = minimum();
//删除最大值后,将该二分搜索树重新赋值给root
root = removeMin(root);
return min;
}
//返回删除最小节点后的node
public Node removeMin(Node node){
//终止条件
if(node.left == null){
Node rightNode = node.right;
node.right = null;
size--;
return rightNode;
}
//相当于如果node.left是最小的节点,
//使node.left = node.left.right
node.left = removeMin(node.left);
return node;
}
//删除最大根节点
public E removeMax(){
E e = maximum();
root = removeMax(root);
return e;
}
//删除最大元素
public Node removeMax(Node node){
//终止条件
if(node.right == null){
Node leftNode = node.left;
node.left = null;
size--;
return leftNode;
}
//如果node.right是最大值,则删除该最大值
//并使node.right重新指向node.right.left
node.right = removeMax(node.right);
return node;
}
/*
* 删除二分搜索树中的任意元素
* 1、终止条件:e等于node.e,
* 如果node.left==null,返回node.right;
* 如果node.right==null,返回node.left;
* 如果都不为空,则返回右子树中最小节点做为根的新的二分搜索树;
* 2、如果不满足终止条件,则需要递归调用
* 如果e
* 如果e>node.e,则递归调用的返回值做为新的node.right;
* * */
public void removeElement(E e){
root = removeElement(root,e);
}
private Node removeElement(Node node,E e){
//1终止条件
if(e.compareTo(node.e) == 0){
if(node.left == null){
size--;
return node.right;
}else if(node.right == null){
size--;
return node.left;
}else{
Node rightNode = node.right;
//获取右子树的最小值
Node min = minumum(rightNode);
//删除右子树的最小值,并返回最新的右子树
Node newRightNode = removeMin(rightNode);
//以min为根,创建新的二分搜索树
min.left = node.left;
min.right = newRightNode;
//此步不需要size--,因为removeMin的时候已经进行了一次size--
//size--;
return min;
}
/*//如果右子树不为null
if(node.right != null){
Node rightNode = node.right;
//获取右子树的最小值
Node min = minumum(rightNode);
//删除右子树的最小值,并返回最新的右子树
Node newRightNode = removeMin(rightNode);
//以min为根,创建新的二分搜索树
min.left = node.left;
min.right = newRightNode;
size--;
return min;
}else{
//如果右子树为null,则返回左子树
size--;
return node.left;
}*/
}
//2如果不满足终止条件
if(e.compareTo(node.e) < 0){
//如果e
if(node.left != null){
node.left = removeElement(node.left,e);
}
}else{
//如果e>node.e,则node.right指向递归调用新城的新的二分搜索树
if(node.right != null){
node.right = removeElement(node.right,e);
}
}
return node;
}
}
测试代码如下:
package tree;
import java.util.ArrayList;
import java.util.Random;
public class Main {
public static void main(String[] args) {
BST
int[] arr = {5,3,6,8,4,2};
for(int i=0;i
bst.add(arr[i]);
}
//前序遍历
bst.preOrder();
//前序遍历的方式写的toString()
System.out.println(bst);
//中序遍历,从小到达输出
bst.middleOrder();
//后续遍历
bst.postOrder();
//非递归的前序遍历
System.out.println("非递归的前序遍:");
bst.preOrderUserStack();
//层序遍历,使用队列实现
System.out.println("层序遍历:");
bst.levelOrder();
//测试removeMin()方法是否正确
testRemoveMin();
//测试删除指定元素
System.out.println("测试删除指定元素:");
bst.removeElement(2);
bst.levelOrder();
//System.out.println(bst.getSize());
}
public static void testRemoveMin(){
Random random = new Random();
BST
for(int i=0;i<1000;i++){
bst.add(random.nextInt(10000));
}
ArrayList
while(!bst.isEmpty()){
arr.add(bst.removeMin());
}
System.out.println(arr);
for(int i=0;i
throw new IllegalArgumentException("Error");
}
}
System.out.println("removeMin is right.");
}
}
小总结:栈可用在前序遍历(深度优先遍历),队列可用在层序遍历(广度优先遍历)
如有理解不到之处,望指正!