二叉搜索树(Binary Search Tree)
二叉搜索树也叫二叉查找树。其有以下特点:
根节点大于左叶子节点,小于右叶子节点。这一特性使得其中序遍历是有序的。因为有序性其搜索效率较高。
先定义一个节点,后面树的查找构建等操作基于该节点定义
static class Node {
int val;
Node left;
Node right;
Node parent;
public Node(int val){
this.val = val;
}
}
查找
对于树中的任意节点X,X的左子树中的所有节点都小于X,X的右子树中的所有节点都大于X。这个有序性质使得我们可以通过比较大小来快速定位和查找节点。对于给定值,多次使用二分递归查找很快能查找定位到该值。
Node find(Node root, int val){
if(root == null) return null;
if(root.val > val){
return find(root.left,val);
}else if(root.val < val){
return find(root.right,val);
}
return root;
}
上面代码只需要从根节点开始比较,然后二分递归查找即可。
插入
插入也是需要先定位到新节点要插入的位置,然后设置下对应的指针引用。
-
如果二叉搜索树为空,则新节点成为根节点,插入完成。
-
如果二叉搜索树不为空,则从根节点开始比较新节点的值与当前节点的值的大小关系。
case1-新节点的值小于当前节点的值,将其插入到当前节点的左子树中。
case2-新节点的值大于当前节点的值,将其插入到当前节点的右子树中。
-
递归地重复上述步骤,直到找到一个合适的位置将新节点插入。
void insert(Node node){ if(null == root){//树为空 root = node;return; } doInsert(root,node); } void doInsert(Node root,Node node){ if(root.val > node.val){ if(root.left == null){ root.left = node; node.parent = root; }else{ doInsert(root.left,node); } } if(root.val < node.val){ if(root.right == null){ root.right = node; node.parent = root; }else{ doInsert(root.right,node); } } }
删除
删除操作可能稍微复杂一定,要考虑几种情况
1.要删除的结点是叶子结点 O(1)
直接删除
2.要删除的结点只有一个子树(左或者右)O(1)
子节点替换当前节点,然后删除子节点
3.要删除的结点有两颗子树:找后继结点,而且后继结点的左子树一定为空。这里的后继节点是中序遍历有序的第一个后继节点。
将后继节点放到当前节点,然后后继节点原来位置移除
具体实现
void delete(int val){
delete(root,val);
}
void delete(Node root,int val){
Node node = find(root,val);
if(null == node) return;
if(node.left == null && node.right == null){
Node p = node.parent;
//父节点为null,当前p为根节点
if(p == null){
this.root = null;return;
}
if(p.left == node) p.left = null;
if(p.right == node) p.right = null;
}else if(node.left == null || node.right == null){
Node p = node.parent;
//父节点为null,当前p为根节点
Node child = node.left == null ?node.right:node.left;
if(p == null){
this.root = child;return;
}
child.parent = p;
if(p.left == node) p.left = child;;
if(p.right == node) p.right = child;
}else{//两个子节点
/**
* 找到后继节点(中序遍历第一个节点),将后继节点的值赋给当前节点,删除后继节点
*/
Node successor = findMinNode(node.right);
node.val = successor.val;
delete(node.right,node.val);
}
}
遍历
对于二叉搜索树,其中序遍历是有序的,其它遍历不考虑
中序遍历实现:
void inorderPrint(Node root){
if(root != null ){
printNode(root);
inorderPrint(root.left);
inorderPrint(root.right);
}
}
void printNode(Node node){
int left = node.left==null?0:node.left.val;
int right = node.right==null?0:node.right.val;
System.out.println(node.val+",left:"+left+",right:"+right);
}
上面指定二叉搜索树有很高的查找和插入效率,但是,如果二叉搜索树的插入和删除操作不平衡或者有序性被破坏,树的高度可能会增长,导致性能下降,甚至退化为链式结构。为了避免这种情况,可以使用平衡二叉搜索树(如红黑树、AVL树)来保持树的平衡性。这样可以保证搜索、插入和删除等操作的时间复杂度始终是O(log n)。下篇红黑树见。