之前讲了部分集合源码,有涉及到红黑树,一下就讲红黑树有点不好讲,先从简单点的树开始讲,以下就从二叉查找树开始讲
一、 二叉查找树
二叉查找树(Binar Search Tree),又称二叉搜索树,二叉排序树
二叉查找树特性:
- 若左子树非空,则左子树上所有的节点关键字值均小于根节点的关键字值
- 若右子树非空,则右子树上所有节点关键字值均大于根节点的关键字值
- 左右子树本身也分别是一颗二叉查找树
简单来说就是左子树节点值<根节点值<右子树节点值。
如下图所示就是一个二叉查找树:
二、二叉查找树的增删改查
二叉搜索树的节点定义
public class Node {
public int data;
public Node left;
public Node right;
public Node(int data) {
this.data = data;
this.left = null;
this.right = null;
}
@Override
public String toString() {
return "Node{" +
"data=" + data +
'}';
}
}
2.1 二叉查找树的插入
- 若二叉查找树为空,则直接插入节点,即根节点
- 若插入的节点值小于根节点值,则插入左子树
- 若插入的节点值大于根节点值,则插入右子树
- 新插入的节点一定是一个叶节点
public class BinarySearchTree {
private Node root;
public BinarySearchTree() {
root = null;
}
public boolean insert(int val){
Node newNode = new Node(val);
if(root == null){
root = newNode;
}else {
Node temp = root;
Node parent;
while(true){
parent = temp;
if(val < temp.data){
temp = temp.left;
if(temp == null){
parent.left = newNode;
return true;
}
}else if (val > temp.data){
temp = temp.right;
if(temp == null){
parent.right = newNode;
return true;
}
}else{
return false;
}
}
}
return false;
}
}
2.2 二叉查找树的查找
二叉查找树的查找先从根节点开始,进行比较向下查找。根据二叉查找树的特性,最小值必然在左子树的最左侧节点,最大值必然在右子树的最右侧节点。
而其他节点的查找则根据查找数与根节点大小的比较,比根节点小的则查找根节点的左子树,比根节点大的则查找根节点的右子树
public Node search(int val) {
Node temp = root;
while (val != temp.data) {
//判断查询方向
if (val < temp.data) {
temp = temp.left;
if(temp == null){
return null;
}
}
if (val > temp.data) {
temp = temp.right;
if(temp == null){
return null;
}
}
}
return temp;
}
比较简单,看下就懂了,不多说了
2.3 二叉查找树的删除
删除过程比较复杂
在删除时,只能删除需要删除的节点,而该节点下的子树不能删除,删除后有可能又破坏了二叉查找树的构成条件,需要重新构造一下
因此删除分为以下几种情况:
- 删除叶节点:直接删除即可,不会破坏二叉查找树的构成
- 删除节点只有一颗左子树或右子树:直接让删除节点的子树替代删除节点的位置
- 删除节点有左右两颗子树:使用删除节点后,中序第一个子女填补
情况1:删除叶节点
public void delete(int val) {
Node temp = root;
Node parent = null;
String position = null;
while (val != temp.data) {
if (val < temp.data) {
parent = temp;
position = "left";
temp = temp.left;
if (temp == null) {
return;
}
}
if (val > temp.data) {
parent = temp;
position = "right";
temp = temp.right;
if (temp == null) {
return;
}
}
}
//情况1:检查删除的节点是否是叶子节点
if (isLoof(temp)) {
if (position.equals("left")) {
parent.left = null;
} else {
parent.right = null;
}
return ;
}
}
删除需要先查找再删除,暂时想到这个比较笨的code,有好的可以分享出来。逻辑比较简单,不多解释了。
情况2:删除节点只有一颗子树
删除节点只有1颗子树只有以下图示得四种情况
根据上面的四种类型,在删除方法里增加以下代码
//判断是否是情况2
Node next = isLoofParent(temp);
if (next != null) {
if (position.equals("left")) {
parent.left = next;
} else {
parent.right = next;
}
temp.left = null;
temp.right = null;
return ;
}
isLoofParent(Node temp)的代码
private Node isLoofParent(Node temp) {
if (temp.left == null && temp.right != null) {
temp = temp.right;
return temp;
}
if (temp.left != null && temp.right == null) {
temp = temp.left;
return temp;
}
return null;
}
代码还是比较low的,逻辑也简单,不解释了。
情况3:左右都有子树
按中序找删除节点后面节点的第一个子女,填补删除节点,然后删除删除节点后面节点的第一个子女。说的有点绕,看下面的图:
以上方法,填补后转换为情况1或情况2
附上完整的删除方法,都比较简单,因为情况有限,每种情况做了if就找到了,就是感觉有点low
public void delete(int val) {
Node temp = root;
Node parent = null;
String position = null;
while (val != temp.data) {
if (val < temp.data) {
parent = temp;
position = "left";
temp = temp.left;
if (temp == null) {
return;
}
}
if (val > temp.data) {
parent = temp;
position = "right";
temp = temp.right;
if (temp == null) {
return;
}
}
}
//情况1:检查删除的节点是否是叶子节点
if (isLoof(temp)) {
if (position.equals("left")) {
parent.left = null;
} else {
parent.right = null;
}
return ;
}
//判断是否是情况2
Node next = isLoofParent(temp);
if (next != null) {
if (position.equals("left")) {
parent.left = next;
} else {
parent.right = next;
}
temp.left = null;
temp.right = null;
return ;
}
//检查是否是情况3
if (temp.left != null && temp.right != null) {
parent = temp;
position = "right";
Node rNode = temp.right;
while (rNode.left != null) {
parent = rNode;
position = "left";
rNode = rNode.left;
}
int tempData = rNode.data;
delete(rNode.data);
temp.data = tempData;
}
}
2.4 二叉查找树的遍历
遍历树可以使用递归,很简单方便,但是可能执行效率稍低。
中序遍历的代码如下,前序和后序只需要换下输出语句的位置即可
public void inOrder() {
this.inOrder(root);
}
private void inOrder(Node root) {
if (root == null) {
return;
}
inOrder(root.left);
System.out.println(root.data + " ");
inOrder(root.right);
}
三、二叉查找树的查找效率分析
对一个高度为h的二叉查找树来说,查找、删除和插入的时间都是O(h)。效率主要取决于树的高度,相当于2分查找,平均查找长度为O(log2n)(不知道怎么把n上标,理解就好)
因此二叉查找树的查找效率也是比较高的,与2分法还有点不同。二分法的查找树是唯一的,但是二叉查找树会根据插入的不同顺序生成不同的树。
有的时候二叉查找树会生成一种极端情况,如下所示:
在这种情况下,相当于是一个链表,查找效率就会受到严重影响。
为了解决这个问题,又出现了平衡二叉树,下一节说一下平衡二叉树