二叉查找树
二叉查找树(Binary Search Tree)也叫排序树或有序树或搜索树,它是为实现快速查找而生。二叉查找树的左子树的节点都小于它的父节点,右子树中的节点都大于它的父节点,因此若按中序遍历则要进行从小到大的排序。
无论是空树,二叉查找树,都有严格的定义:
若左子树不空,则左树上所有节点的值均小于它的根节点的值;若右子树不空,则右树上所有节点的值均小于它的根节点的值;左、右树也分别为二叉查找树,没有键值相等的节点。
二叉树查找的时间复杂度最坏的情况下类似链表为O(n),而在一般平衡状态下一般都会接近O(logn),从时间复杂度来看是一种比较出色的查找的数据结构。而在插入操作中由于只能插入在叶子上也类似查找的时间复杂度。但是二叉查找树在删除时要分为三种情况:
1、被删除结点没有子结点:直接将其父结点对应的左子树或右子树设置为 null。如删除结点 E 时,pp.left = null 或 pp.right = null。
2、被删除结点只有一个子结点:直接将其父结点指向被删除结点的左子树或右子树。如删除结点 D 时,pp.left = p.left 或 pp.right = p.right。
3、被删除结点有两个子结点:此时删除比较复杂,需要先从被删除结点的右子树查找最小值结点 p2 赋值给被删除结点。这样就变成删除这个右子树查找最小值结点 p2 的问题,同第一种情况完全一样。如删除结点 B 时,需要在其右子树中查找一个最小的值结点 F,将 B 对应结点的值替换成 F,然后再删除 F。
二叉查找树的时间复杂度都和树的高度息息相关,也就是 O(height)。如果满足平衡二叉树时树高度为 logn,如果退化成链表时树高度为 n,而深度(高度)为k的二叉树至多有2^k-1个结点。
用Java实现二叉树的顺序存储
实现要点:
1、对于完全二叉树,若从上往下,从左到右,则编号为i的结点,其左孩子编号必定为2i,其右边孩子编号必定为2i+1,其双亲结点编号必定为i/2.
2、第i层有2^(k-1)个结点(k为深度)
3、对于任何一棵二叉树,若2度的结点数有n2个,则叶子树n0必定为n2+1(即n0=n2+1)
实现代码:
import java.util.Scanner;
public class BinaryTree {
final private int DEFAULT_PEEK=6;//创建使用final防止继承
private int deep;//采用private作为访问控制修饰符
private int length;
private String[] array;
public static void main(String[] args) {
BinaryTree bin=new BinaryTree();//new一个新的对象,在系统中申请空间
bin.addNode();//调用addNode方法初始化节点
System.out.println("左节点是:");
System.out.println(bin.getLeftNode(2));//调用getLeftNode方法来返回左节点
System.out.println("右节点是:");
System.out.println(bin.getRightNode(2));//调用getRightNode方法来返回右节点
System.out.println("====================");//分割线
bin.addNodeLeft(2, "insert");//调用addLeftNode方法来添加左节点
bin.addNodeRight(2, "insert");//调用addRightNode方法来添加右节点
System.out.println("插入后左节点是:");
System.out.println(bin.getLeftNode(2));//调用getLeftNode方法来返回左节点
System.out.println("====================");
System.out.println("插入后右节点是:");
System.out.println(bin.getRightNode(2));//调用getRightNode方法来返回右节点
}
/**
* 无参构造器
*/
public BinaryTree() {
this.deep=DEFAULT_PEEK;//定义deep为设定好的"6"
this.length=(int)(Math.pow(2,deep)-1);//设定节点数
array=new String[length];//存储节点数在array中
}
/**
* 传入深度参数的构造器
*/
public BinaryTree(int deep) {
this.deep=deep;
this.length=(int)(Math.pow(2,deep)-1);
array=new String[length];
}
/**
* 传入参数深度和根结点的构造器
*/
public BinaryTree(int deep,String data) {
this.deep=deep;
this.length=(int)(Math.pow(2,deep)-1);
array=new String[length];
array[1]=data;//传入根节点
}
/**
* 添加初始化结点
*/
public void addNode() {
Scanner input=new Scanner(System.in);//新建一个键盘扫描器
System.out.println("请输入二叉树信息,输入值为0结束输入");
for (int i = 1; i < array.length; i++) {
if (array[i]==null||array[i].equals("0")==true) {
//判定为空则输入节点
array[i]=input.nextLine();//nextline()读取到回车结束(但是会吸收回车的数据)
if (array[i].equals("0")==true) {
//判定输入为0则结束
break;
}
}
}
}
/**
* 判断二叉树是否为空
*/
public boolean isEmpaty() {
if (array[1]!=null&&array[1].equals("0")!=true) {
//判定剩余的节点不为空且为0则为空树·
return false;
}else {
return true;
}
}
/**
* 返回指定结点的值
*/
public String returnData(int index) {
if (index<1||index>array.length-1) {
//判断是否存在该节点
return null;
}else {
return array[index];
}
}
/**
* 返回指定结点的父节点
*/
public String getParent(int index) {
if (index<1||index>array.length-1) {
//判断是否存在该节点
return null;
}else {
return array[index/2];
}
}
/**
* 添加指定结点的左节点
*/
public void addNodeLeft(int index,String data) {
if (array[2*index]!=null&&array[2*index].equals("0")!=true) {
//判断左节点不为空且不为0
System.out.println("当前结点左节点已有值,是否覆盖?Y/N");
Scanner input=new Scanner(System.in);//新建一个键盘扫描器
String in=input.nextLine();//nextline()读取到回车结束(但是会吸收回车的数据)
if (in.equals("Y")) {
array[index*2]=data;//将输入数据存入该节点中
}else if (in.equals("N")) {
return;
}
}
}
/**
* 添加指定结点的右节点
*/
public void addNodeRight(int index,String data) {
if (array[2*index+1]!=null&&array[2*index+1].equals("0")!=true) {
//判断右节点不为空且不为0
System.out.println("当前结点右节点已有值,是否覆盖?Y/N");
Scanner input=new Scanner(System.in);//新建一个键盘扫描器
String in=input.nextLine();//nextline()读取到回车结束(但是会吸收回车的数据)
if (in.equals("Y")) {
array[index*2+1]=data;//将输入数据存入该节点中
}else if(in.equals("N")) {
return;
}
}
}
/**
* 返回指定结点的左节点
*/
public String getLeftNode(int index) {
if (array[2*index]!=null&&array[2*index].equals("0")!=true) {
//判断左节点不为空且不为0
return array[index*2];
}
else {
return null;
}
}
/**
* 返回指定结点的右节点
*/
public String getRightNode(int index) {
if (array[2*index+1]!=null&&array[2*index+1].equals("0")!=true) {
//判断右节点不为空且不为0
return array[index*2+1];
}
else {
return null;
}
}
}
运行结果截图:
二叉查找树对比散列表(哈希表)
在不考虑哈希冲突的情况下散列表的插入、删除、查找的时间复杂度都是 O(1),而二叉查找树在平衡情况下为O(logn)。那么二叉查找树的优势相对于散列表在哪里呢?
1、散列表有一个最明显的问题就是其中的数据均为无序数据,如果我们对一组数据的顺序又要求时,我们要对散列表中的顺序进行排列,而二叉查找树法仅需要进行中序遍历就可获得有序数据。
2、散列表扩容耗时很多,而且当遇到散列冲突时,性能不稳定。而尽管二叉查找树的性能不稳定,但是在工程中,我们最常用的平衡二叉查找树的性能非常稳定,时间复杂度稳定在 O(logn)。
3、尽管散列表的查找等操作的时间复杂度是常量级的,但因为哈希冲突的存在,这个常量不一定比 logn 小,所以实际的查找速度可能不一定比 O(logn) 快。加上哈希函数的耗时,也不一定就比平衡二叉查找树的效率高。
4、散列表的构造比二叉查找树要复杂,需要考虑的东西很多。比如散列函数的设计、冲突解决办法、扩缩容等。平衡二叉查找树只需要考虑平衡性这一个问题,而且这个问题的解决方案比较成熟、固定。
所以我们面对一个数据大小不太大不容易发生哈希冲突且对速度有高要求的问题时,可以采用散列表。但是在解决对速度稳定性有高要求的问题时,便可以考虑使用二叉查找树这个稳定的解决方案。
引用链接:
二叉查找树对比哈希表
java实现二叉树的顺序存储