1 二叉搜索树
二叉搜索树是指一棵树的左子树上所有的节点都小于它的根节点;右子树上所有的节点都大于它的根节点。且对于它的所有子树都是如此。基于这个性质,当我们对二叉搜索树进行中序遍历时,就可以得到有序的序列。
1.1 基本操作
新增
根据二叉搜索树的性质,当需要新增元素时,我们只需要判断这个元素是大于根节点还是小于根节点。如果是大于,则在右子树中继续寻找;如果是小于,则在左子树中继续寻找,直到找到适合自己的空位。
代码如下:
/**
* 新增
* @param data
*/
public void add(int data){
// 用于判断新增的位置是左孩子还是右孩子
boolean isLeft = true;
// 父节点
BSTreeNode parent = null;
// 当前节点
BSTreeNode cur = head;
while(cur != null){
parent = cur;
// 比当前节点大,往右找
if(data > cur.data){
cur = cur.right;
isLeft = false;
}else if(data < cur.data){
// 比当前节点小,往左找
cur = cur.left;
isLeft = true;
}else{
// 已存在直接返回
return;
}
}
// 生成节点,便于子类扩展
BSTreeNode newNode = createNode(data);
if(isLeft){
parent.left = newNode;
}else{
parent.right = newNode;
}
newNode.parent = parent;
// 新增后处理方法,用于子类扩展
afterAdd(newNode);
}
查找
查找就比较简单了,跟新增有些类似。从根节点开始,如果当前节点小于新节点,就从右树上继续查找;如果当前节点大于新节点,就从左树上继续查找;
代码如下:
/**
* 查询
* @param data
* @return
*/
public BSTreeNode search(int data){
BSTreeNode cur = head;
while(cur != null){
// 比当前节点大,向右找
if(data > cur.data){
cur = cur.right;
}else if(data < cur.data){
// 比当前节点小,向左找
cur = cur.left;
}else{
// 找到后返回
return cur;
}
}
// 找不到返回空
return null;
}
删除
删除比较复杂,需要分情况讨论:
(1)待删除节点没有左、右孩子,直接删除即可。
(2)待删除节点只有左孩子或只有右孩子,让子节点代替自己的位置。
(3)待删除节点既有左孩子又有右孩子,需要找到待删除节点的左子树的最右节点或右子树的最左节点K,将K从原本的位置转移到待删除节点的位置。
前两中情况比较好理解,下面着重解释一下第三种情况。
当我们删除一个节点时,仍然需要保持二叉搜索树的性质,即任何一个节点的左子树上的节点都小于根节点,右子树上的节点都大于根节点。
为了保持这个性质,我们就需要从它的子树中找到一个节点K,K必须比左子树上的节点都要大,比右子树上的节点都要小。然后让K代替自己的位置。
图中满足条件的节点有两个:14和17。
其实我们只需要对整棵树进行中序遍历就可以发现,14和17分别在待删除节点15的左右两侧。
中序遍历:3 5 7 9 10 12 13 14 15 17
这样一来,为什么要用这两个节点替代待删除节点也就清晰明了了。
代码如下:
/**
* 删除
* @param data
*/
public void delete(int data){
BSTreeNode node = search(data);
if(node == null){
return;
}
// 如果节点没有左子树,让右子树接替自己的位置
if(node.left == null){
if(node.parent.left == node){
node.parent.left = node.right;
}else{
node.parent.right = node.right;
}
}else if(node.right == null){
// 如果节点没有右子树,让左子树接替自己的位置
if(node.parent.left == node){
node.parent.left = node.left;
}else{
node.parent.right = node.left;
}
}else{
// 如果既有左子树又有右子树
// 找到左子树的最右节点,也就是[替代节点]
BSTreeNode cur = node.left;
while(cur.right != null){
cur = cur.right;
}
// 用[替代节点]替换待删除节点
node.data = cur.data;
// 删除原本的[替代节点]
// 如果左子树没有最右节点,即cur不变
if(cur == node.left){
// 直接把[替代节点]的左树接到父节点上
cur.parent.left = cur.left;
}else{
// 否则把[替代节点]的左树接到父节点上
cur.parent.right = cur.left;
}
}
}
1.2 扩展操作
之所以称之为扩展操作,是因为经典的二叉搜索树并不需要这些操作,但是由经典二叉搜索树衍生出的红黑树、AVL树、SB树等会频繁用到这些操作。
左旋
左旋就是让原本的根节点(H)的右孩子(R)作为新的根,此时H会成为R的左孩子,而R原本的左孩子就需要调整位置,成为H的右孩子。
代码如下:
/**
* 左旋
* @param node
*/
public void leftRotate(BSTreeNode node){
// 先找到根节点的右孩子
BSTreeNode R = node.right;
// 没有右子树无法左旋
if(R == null){
return;
}
// R的左孩子成为根节点的右孩子
node.right = R.left;
if(R.left != null){
R.left.parent = node;
}
// 根节点成为R的左孩子
R.left = node;
R.parent = node.parent;
if(node.parent != null){
if(node == node.parent.left){
node.parent.left = R;
}else{
node.parent.right = R;
}
}
node.parent = R;
// 如果是整棵树的根节点,重新赋值head
if(node == head){
head = R;
}
}
右旋
与左旋相对,右旋就是让原本的根节点(H)的左孩子(L)作为新的根,此时H会成为L的右孩子,而L原本的右孩子就需要调整位置,成为H的左孩子。
代码如下:
/**
* 右旋
* @param node
*/
public void rightRotate(BSTreeNode node){
// 先找到根节点的左孩子
BSTreeNode L = node.left;
// 没有左子树无法右旋
if(L == null){
return;
}
// L的右孩子成为根节点的左孩子
node.left = L.right;
if(L.right != null){
L.right.parent = node;
}
// 根节点成为L的右孩子
L.right = node;
L.parent = node.parent;
if(node.parent != null){
if(node == node.parent.left){
node.parent.left = L;
}else{
node.parent.right = L;
}
}
node.parent = L;
// 如果是整棵树的根节点,重新赋值head
if(node == head){
head = L;
}
}
1.3 代码实现
下面是完整的代码实现:
/**
* 二叉搜索树节点类
*/
public class BSTreeNode {
/**
* 左孩子
*/
public BSTreeNode left;
/**
* 右孩子
*/
public BSTreeNode right;
/**
* 父节点
*/
public BSTreeNode parent;
/**
* 数据域
*/
public int data;
/**
* 构造方法
* @param data
*/
public BSTreeNode(int data){
this.data = data;
}
}
/**
* 二叉搜索树
*/
public class BSTree {
/**
* 根节点
*/
private BSTreeNode head;
public BSTree(int data){
head = createNode(data);
}
/**
* 创建一个节点
* @param data
* @return
*/
protected BSTreeNode createNode(int data){
return new BSTreeNode(data);
}
/**
* 删除
* @param data
*/
public void delete(int data){
BSTreeNode node = search(data);
if(node == null){
return;
}
// 如果节点没有左子树,让右子树接替自己的位置
if(node.left == null){
if(node.parent.left == node){
node.parent.left = node.right;
}else{
node.parent.right = node.right;
}
}else if(node.right == null){
// 如果节点没有右子树,让左子树接替自己的位置
if(node.parent.left == node){
node.parent.left = node.left;
}else{
node.parent.right = node.left;
}
}else{
// 如果既有左子树又有右子树
// 找到左子树的最右节点,也就是[替代节点]
BSTreeNode cur = node.left;
while(cur.right != null){
cur = cur.right;
}
// 用[替代节点]替换待删除节点
node.data = cur.data;
// 删除原本的[替代节点]
// 如果左子树没有最右节点,即cur不变
if(cur == node.left){
// 直接把[替代节点]的左树接到父节点上
cur.parent.left = cur.left;
}else{
// 否则把[替代节点]的左树接到父节点上
cur.parent.right = cur.left;
}
}
}
/**
* 查询
* @param data
* @return
*/
public BSTreeNode search(int data){
BSTreeNode cur = head;
while(cur != null){
// 比当前节点大,向右找
if(data > cur.data){
cur = cur.right;
}else if(data < cur.data){
// 比当前节点小,向左找
cur = cur.left;
}else{
// 找到后返回
return cur;
}
}
// 找不到返回空
return null;
}
/**
* 新增
* @param data
*/
public void add(int data){
// 用于判断新增的位置是左孩子还是右孩子
boolean isLeft = true;
// 父节点
BSTreeNode parent = null;
// 当前节点
BSTreeNode cur = head;
while(cur != null){
parent = cur;
// 比当前节点大,往右找
if(data > cur.data){
cur = cur.right;
isLeft = false;
}else if(data < cur.data){
// 比当前节点小,往左找
cur = cur.left;
isLeft = true;
}else{
// 已存在直接返回
return;
}
}
BSTreeNode newNode = createNode(data);
if(isLeft){
parent.left = newNode;
}else{
parent.right = newNode;
}
newNode.parent = parent;
afterAdd(newNode);
}
/**
* 新增成功后触发
*/
protected void afterAdd(BSTreeNode node){
}
/**
* 左旋
* @param node
*/
public void leftRotate(BSTreeNode node){
// 先找到根节点的右孩子
BSTreeNode R = node.right;
// 没有右子树无法左旋
if(R == null){
return;
}
// R的左孩子成为根节点的右孩子
node.right = R.left;
if(R.left != null){
R.left.parent = node;
}
// 根节点成为R的左孩子
R.left = node;
R.parent = node.parent;
if(node.parent != null){
if(node == node.parent.left){
node.parent.left = R;
}else{
node.parent.right = R;
}
}
node.parent = R;
// 如果是整棵树的根节点,重新赋值head
if(node == head){
head = R;
}
}
/**
* 右旋
* @param node
*/
public void rightRotate(BSTreeNode node){
// 先找到根节点的左孩子
BSTreeNode L = node.left;
// 没有左子树无法右旋
if(L == null){
return;
}
// L的右孩子成为根节点的左孩子
node.left = L.right;
if(L.right != null){
L.right.parent = node;
}
// 根节点成为L的右孩子
L.right = node;
L.parent = node.parent;
if(node.parent != null){
if(node == node.parent.left){
node.parent.left = L;
}else{
node.parent.right = L;
}
}
node.parent = L;
// 如果是整棵树的根节点,重新赋值head
if(node == head){
head = L;
}
}
/**
* 中序遍历
* @return
*/
@Override
public String toString() {
StringBuffer buffer = new StringBuffer();
Stack<BSTreeNode> stack = new Stack<>();
BSTreeNode cur = head;
while(cur != null || !stack.isEmpty()){
if(cur != null){
stack.push(cur);
cur = cur.left;
}else{
cur = stack.pop();
buffer.append(cur.data+" ");
cur = cur.right;
}
}
return buffer.toString();
}
/**
* 打印整棵树
*/
public void printTree(){
Stack<BSTreeNode> stack = new Stack<>();
BSTreeNode cur = head;
stack.push(cur);
while(!stack.isEmpty()){
cur = stack.pop();
if(cur.left != null){
System.out.println(cur.data +" -l-> "+cur.left.data);
stack.push(cur.left);
}
if(cur.right != null){
System.out.println(cur.data +" -r-> "+cur.right.data);
stack.push(cur.right);
}
}
}
}