二叉搜索树,即Binary Search Tree.二叉搜索树又叫二叉查找树,或者二叉排序树。
二叉搜索树要求,对于树中每一个节点,都要求左子树每个节点的值都小于当前节点值,右子树的每个节点值都大于当前节点的值。
二叉查找树的查找操作
我们从根节点开始查找,如果当前节点等于我们要查找的数据,那就返回。如果查找的数据比根节点的数据小,那就从左子树中递归查找。如果要查找的数据比根节点的值大,那就在右子树中递归查找。
二叉查找树的插入操作
新插入的数据我们放在叶子节点上,从根节点开始,依次比较插入的数据和节点的大小关系。如果插入的数据比当前节点大,并且当前节点的右子树为空则将新数据直接插入到当前节点的右子节点处。如果不为空则遍历查找右子树查找插入位置。如果插入的数据比当前节点的数据小,并且当前节点的左子树为空则将新数据直接插入到当前节点的左子节点处。如果不为空则遍历查找左子树插入位置。
二叉查找树的删除操作
分三种情况:
①要删除的节点没有子节点,那么我们只需要将父节点中指向这个节点的指针置位null即可。
②要删除的节点只有一个子节点,左子节点或者右子节点。我们只需要将父节点中指向这个节点的指针指向这个节点的子节点。
③删除的节点有两个子节点,则需要找到这个节点右子树中的最小节点,把它替换到要删除的节点上。然后再删除这个最小节点,利用①和②的规则
代码
package com.study.algorithm.tree;
/**
* @Auther: JeffSheng
* @Date: 2019/9/2 14:28
* @Description:
* 二叉树的增删改查操作
*/
public class BinaryTree {
/**
* 在二叉树中查找数据(不考虑重复数据)
* @param p
* @param data
* @return
*/
public static TreeNode find(TreeNode p,int data){
while(p!=null){
if(data < Integer.parseInt(p.data)){
p = p.left;
}else if(data > Integer.parseInt(p.data)){
p = p.right;
}else{
return p;
}
}
return null;
}
/**
*在二叉树搜索树中插入一个数据(不考虑重复数据)
* @param p
* @param data
*/
public static void insert(TreeNode p,int data){
while(p!=null){
if(data > Integer.parseInt(p.data)){
if(p.right==null){
p.right = new TreeNode(data+"");
return;
}
p = p.right;
}else if(data < Integer.parseInt(p.data)){
if(p.left==null){
p.left = new TreeNode(data+"");
return;
}
p = p.left;
}
}
}
/**
* 二叉搜索树的删除操作(不考虑重复数)
* @param p
* @param data
*/
public static void delete(TreeNode p,int data){
//p节点的父节点
TreeNode pParent = null;
while(p!=null && Integer.parseInt(p.data)!=data){
pParent = p;
if(data > Integer.parseInt(p.data)){
p = p.right;
}else{
p = p.left;
}
}
//没有找到要删除的节点
if(p==null){
return;
}
//要删除的节点有两个子节点
if(p.left!=null && p.right!=null){
//查找右子树中的最小节点
TreeNode minNode = p.right;
//右子树中最小节点的父节点
TreeNode minParentNode = null;
while(minNode.left!=null){
minParentNode = minNode;
minNode = minNode.left;
}
//将右子树中找到的最小节点值替换到p(将要删除的那个节点)中
p.data = minNode.data;
//把右子树的那个最小值(没有左子树)删除掉,只需要其父节点的左子节点指向null即可
p = minNode;
pParent = minParentNode;
pParent.left=null;
}else{
//删除的节点是叶子节点或者仅有一个子节点
//待删除节点p的子节点
TreeNode child = null;
if(p.left!=null){
child=p.left;
}else if(p.right!=null){
child = p.right;
}else{
child=null;
}
//待删除节点的父节点空,说明待删除节点时根节点,无父节点
if(pParent==null){
p=child;
}else if(pParent.left==p){
//待删除节点时其父节点的左子节点,那么将父节点新的'左子节点'(不能是右子节点)指向待删除节点之前的child子节点,因为删除了左子节点必然要补一个左子节点
pParent.left = child;
}else{
//待删除节点时其父节点的右子节点
pParent.right = child;
}
}
}
/**
* 查询二叉搜索树中的最小值
* @param tree
* @return
*/
public TreeNode findMin(TreeNode tree) {
if (tree == null) {
return null;
}
TreeNode p = tree;
while (p.left != null) {
p = p.left;
}
return p;
}
/**
* 查询二叉搜索树中的最大值
* @param tree
* @return
*/
public TreeNode findMax(TreeNode tree) {
if (tree == null) {
return null;
}
TreeNode p = tree;
while (p.right != null) {
p = p.right;
}
return p;
}
public static void main(String[] args) {
TreeNode root = new TreeNode("33");
TreeNode a = new TreeNode("16");
TreeNode b = new TreeNode("50");
root.left = a;
root.right = b;
TreeNode d = new TreeNode("13");
TreeNode e = new TreeNode("18");
a.left=d;
a.right=e;
TreeNode f = new TreeNode("34");
TreeNode g = new TreeNode("58");
b.left=f;
b.right=g;
TreeNode h = new TreeNode("15");
d.right = h;
TreeNode i = new TreeNode("17");
TreeNode j = new TreeNode("25");
e.left = i;
e.right = j;
TreeNode k = new TreeNode("19");
TreeNode l = new TreeNode("27");
j.left=k;
j.right=l;
TreeNode m = new TreeNode("51");
TreeNode n = new TreeNode("66");
g.left=m;
g.right=n;
TreeNode o = new TreeNode("55");
m.right=o;
TreeTraversal.inTraversal(root);
insert(root,56);
System.out.println("-------------------------");
TreeTraversal.inTraversal(root);
delete(root,55);
System.out.println("-------------------------");
TreeTraversal.inTraversal(root);
}
}
二叉查找树的其他操作
二叉查找树可以找到树中的最大值、最小值,还可以通过中序遍历对树中的数据进行从小到大排序。时间复杂度O(n),所以二叉查找树又叫二叉排序树。
支持重复数据的二叉查找树
前边都是在没有相同数据的情况下的二叉查找树,对于含有相同数值的二叉查找树有一些方法可以做到。
① 二叉树中每个节点不仅仅是存储一个数据,二是使用数组或链表来存储等值数据。
②每个节点仍然存储一个数据,但是把等值数据存储到节点的右子节点处。也就是等值的当成大于的处理。
在查找的时候:遇到等值数据并不停止查找,二是继续在右子树中查找直到遇到叶子节点,才停止。这样就可以把所有等值数据查找出来。
在删除的时候:需要找到每个等值数据然后按照前边删除的方法依次删除。
二叉查找树的时间复杂度分析
其实二叉查找树的插入、查找、删除操作都跟树的高度成正比O(height),所以这个问题可以转化为二叉查找树的高度问题。
树的高度=最大层数-1
在包含n个节点的完全二叉树中,第一层包含1个节点,第二层包含2个节点,第三层包含4个节点,依次类推,下边一层节点个数是上一层的2倍,第K层就是2^(K-1)个节点,
对于完全二叉树来说最后一层的节点个数其实在1~2^(L-1)之间,其中L是最大层数。把每层节点个数加起来就是n,即
n >= 1+2+4+8+...+2^(L-2)+1
n <= 1+2+4+8+...+2^(L-2)+2^(L-1)
根据等比数列公式求和计算:L 的范围是 [log(n+1), logn +1],所以完全二叉树层数小于等于logn+1,也就是高度小于logn.
所以完全二叉树的二叉查找树,O(height)时间复杂度的小于等于logN.
对于图中的二叉查找树,极度不平衡,其实已经退化成链表了,时间复杂度退化成O(n)。
所以,二叉查找树想要保证稳定的时间复杂度,必须保证二叉查找树是平衡的,即平衡二叉查找树,其查找、删除、新增的时间复杂度都高度接近O(logN)很稳定.。
ps:红黑树就是一种平衡二叉查找树。
想下:散列表O(1)的时间复杂度,为什么还需要二叉查找树BST这种在比较平衡时才是O(logN)的数据结构呢?
1 散列表无序存储,需要排序。而BST排序时间复杂度O(n)
2 散列表扩容耗时多,散列冲突时性能不稳定,二叉平衡树性能稳定O(logn)
3 散列表查找在冲突时时间复杂度虽然是常量级与可能会大于O(logN)
4 散列表构造复杂考虑因素很多,平衡二叉查找树只需要考虑平衡性。