二叉查找树
又叫二叉搜索树,要求在树中的任何一个节点,其左子树中每个节点的值都要小于这个节点的值,右子树节点的值都要大于这个节点的值。
1、查找
思路:
先取根节点,若等于要查找的数据,返回
若小于要查找的数据,递归查找右子树
若大于要查找的数据,递归查找左子树
class Node{
public int data;
public Node left;
public Node right;
public Node(int data, Node left, Node right){
this.data = data;
this.left = left;
this.right = right;
}
@Override
public String toString() {
return "Node{" +
"data=" + data +
", left=" + left +
", right=" + right +
'}';
}
}
public class BinarySearchTree {
//根节点
private Node root;
public BinarySearchTree(Node root){
this.root = root;
}
public Node find(int data){
Node p = root;
while (p != null){
if (p.data > data) p = p.left;
if (p.data < data) p = p.right;
else return p;
}
return null;
}
public static void main(String[] args) {
Node node13 = new Node(27, null, null);
Node node12 = new Node(19, null, null);
Node node11 = new Node(66, null, null);
Node node10 = new Node(51, null, null);
Node node9 = new Node(25, node12, node13);
Node node8 = new Node(16, null, null);
Node node7 = new Node(58, node10, node11);
Node node6 = new Node(34, null, null);
Node node5 = new Node(18, null, node9);
Node node4 = new Node(13, null, node8);
Node node3 = new Node(50, node6, node7);
Node node2 = new Node(17, node4, node5);
Node node1 = new Node(33, node2, node3);
BinarySearchTree tree = new BinarySearchTree(node1);
Node node = tree.find(19);
System.out.println(node.toString());
}
}
结果输出:
Node{data=19, left=null, right=null}
2、插入
思路:
从根节点开始,若要插入的数据比节点数据大,并且节点右子树为空,则将新数据插入到右子节点位置。
若右子树不为空,递归遍历右子树,查找插入位置。
若要插入的数据比节点数据小,并且节点左子树为空,则将新数据插入到左子节点位置。
若左子树不为空,递归遍历左子树,查找插入位置。
插入之前树结构:
Node{data=33, left=Node{data=17, left=Node{data=13,
left=null, right=Node{data=16, left=null, right=null}},
right=Node{data=18, left=null, right=Node{data=25,
left=Node{data=19, left=null, right=null},
right=Node{data=27, left=null, right=null}}}},
right=Node{data=50, left=Node{data=34, left=null,
right=null}, right=Node{data=58, left=Node{data=51,
left=null, right=null}, right=Node{data=66, left=null,
right=null}}}}
public void insert(int data){
Node p = root;
while (p != null){
if (p.data > data){
if (p.left == null){
p.left = new Node(data,null,null);
return;
}
p = p.left;
}else{
if (p.right == null){
p.right = new Node(data,null,null);
return;
}
p = p.right;
}
}
}
插入之后输出:
Node{data=33, left=Node{data=17, left=Node{data=13,
left=null, right=Node{data=16, left=null, right=null}}, right=Node{data=18, left=null, right=Node{data=25,
left=Node{data=19, left=null, right=null},
right=Node{data=27, left=null, right=null}}}},
right=Node{data=50, left=Node{data=34, left=null,
right=null}, right=Node{data=58, left=Node{data=51,
left=null, right=Node{data=55, left=null, right=null}},
right=Node{data=66, left=null, right=null}}}}
3、删除
删除操作相对复杂一点,要分三种情况考虑
要删除的节点没有子节点:直接将父节点指向删除节点的指针置成null即可。
要删除的节点有一个子节点:将父节点指向删除节点的指针置成删除节点的子节点即可。
要删除的节点有两个子节点:找到删除节点右子树的最小节点,最小节点和删除节点值互换,然后删除最小节点即可。最小节点一定没有左子节点。
public void delete(int data){
Node p = root;//指向要删除的节点,初始化为根节点
Node pp = null;//指向要删除节点的父节点。初始化为null
//查找要删除的节点
while (p != null && p.data != data){
pp = p;
if (p.data < data) p = p.left;
else p = p.right;
}
if (p == null) return;
//要删除节点有两个节点
if (p.left != null && p.right != null){
Node minp = p.right;
Node minpp = p;
while (minp.left != null){
minpp = minp;
minp = minp.left;
}
//替换要删除的数据
p.data = minp.data;
//上面替换完,要删除的数据和最小数据一样了,
//剩下的就是删除最小值就可以了
//因为p指向的是要删除的,所以将p指向最小值
p = minp;
pp = minpp;
}
//删除没有子节点或者有一个节点
//上面有两个子节点的问题已经转换成了没有子节点的问题
Node child;//要删除节点P的子节点
if (p.left != null) child = p.left;
else if (p.right != null) child = p.right;
else child = null;
if (pp == null) root = child;//删除根节点
else if (pp.left == p) pp.left = child;
else pp.right = child;
}
4、支持重复数据的二叉查找树
二叉查找树中节点的数据相同怎么办呢?有两种方法:
节点不仅存储数据,要存储一个链表或者数组,相同的数据放到链表或者数组中,类似于散列表。
相同的数据,放到此节点的右子节点中。这种情况在查找、删除的时候就不能找到一个就返回,要一直遍历到最后。
5、时间复杂度分析
在极度恶心的情况下,二叉树左右子树极度不平衡,变成了链表,此时的时间复杂度为O(n)。
在理想情况下,时间复杂度和树的高度成正比,也就变成了二叉树的高度问题。
根据完全二叉树的结构我们可以发现,第一层是1个节点,第二层是2个节点,第三层是4个节点,以此类推第K层就是2(k-1)个节点,而最后一层不一定是这样的,它的节点数在1~2(k-1)之间,那么总的节点数n的值范围就是:
n >= 1+2+4+8+...+2^(K-2)+1
n <= 1+2+4+8+...+2^(K-2)+2^(K-1)
层次K的范围是[log2(n+1), log2n +1],也就是说完全二叉树的层数小于等于log2n +1,树的高度是log2n。最后得出完全二叉树的时间复杂度为O(logn)。
6、二叉查找树和散列表
散列表是无序的,若需要输出有序的数据,需要先排序,而二叉树只需要做一次中序遍历即可。
散列表扩容耗时很多
散列表存在哈希冲突,当哈希冲突到达一定程度时,时间复杂度虽然是常量的,但是是比O(logn)大的。
散列表的构造过程要比二叉树复杂,需要考虑散列函数、冲突、扩容、缩容等问题
7、扩展问题
如何求出一颗二叉树的确切高度?
递归法,根节点高度 = max(左子树高度,右子树高度)+1
原文:https://gper.club/articles/7e7e7f7ff7g54gccg68