The best way to end your fear is to face it yourself.
结束恐惧的最佳办法就是自己面对。
本分分享了二叉搜索树的几种实现,由简入繁。说句题外话,马上又是金三银四的季节了,无论跳不跳槽,增加自己的知识储备总是没错的。从代码里读原理,读思想。
所有源码均已上传至github:链接
日志
2019年3月28日 AVL树
准备
public class Node {
/**
* data
*/
public int data;
/**
* 左节点
*/
public Node left;
/**
* 右节点
*/
public Node right;
public Node(int data) {
this.data = data;
}
}复制代码
实现一个支持插入、删除、查找操作的二叉查找树,并且找二叉树的最大值和最小值操作
插入操作
传入一个data值,当tree为null,初始化tree,否则遍历tree,比较data与node.data的值,大于的话插入右节点,否则插入左节点。
private void insert(int data) {
if (null == tree) {
tree = new Node(data);
return;
}
Node node = tree;
while (null != node) {
if (data > node.data) {
if (null == node.right) {
node.right = new Node(data);
return;
}
node = node.right;
} else {
if (null == node.left) {
node.left = new Node(data);
return;
}
node = node.left;
}
}
}复制代码
查找
查找的实现也比较简单,遍历tree的左右节点,直到找到data == node.data
private Node find(int data) {
Node node = tree;
while (null != node) {
if (data > node.data) {
node = node.right;
} else if (data < node.data) {
node = node.left;
} else {
return node;
}
}
return null;
}复制代码
找二叉树的最大值
找二叉树的最大值最小值比较简单,按照树的结构遍历即可
private Node findMaxNode() {
if (null == tree) return null;
Node node = tree;
while (null != node.right) {
node = node.right;
}
return node;
}复制代码
找二叉树的最小值
private Node findMinNode() {
if (null == tree) return null;
Node node = tree;
while (null != node.left) {
node = node.left;
}
return node;
}复制代码
删除操作
删除是这里面最复杂的操作了,这里详细讲一下。
针对删除的节点的位置不同需要考虑以下三种情况:
- 如果要删除的节点没有子节点,只需要直接将父节点中,指向要删除节点的指针置为 null。
- 如果要删除的节点只有一个子节点(左子节点或者右子节点),只需要更新父节点中,指向要删除节点的指针,让它指向要删除节点的子节点就可以了。
- 如果要删除的节点有两个子节点,这就比较复杂了。需要找到这个节点的右子树中的最小节点,把它替换到要删除的节点上。然后再删除掉这个最小节点,因为最小节点肯定没有左子节点(如果有左子结点,那就不是最小节点了)。
private void delete(int data) {
Node node = tree;
//node的父节点
Node pNode = null;
while (null != node && node.data != data) {
pNode = node;
if (data > node.data) {
node = node.right;
} else {
node = node.left;
}
}
if (null == node) return;
if (null != node.left && null != node.right) {
Node p = node.right;
//p的父节点
Node pp = node;
while (null != p.left) {
pp = p.left;
p = p.left;
}
//删除操作
node.data = p.data;
node = p;
pNode = pp;
}
Node child;
if (null != node.left) {
child = node.left;
} else if (null != node.right) {
child = node.right;
} else {
child = null;
}
if (null == pNode) {
tree = child;
} else if (pNode.left == null) {
pNode.left = child;
} else {
pNode.right = child;
}
}复制代码
实现查找二叉查找树中某个节点的后继、前驱节点
ps:因为这里测试的节点都是根节点,因为比较简单,不需要遍历该节点的父节点。
后续如果需要,欢迎留言,我补充一下。
查找前驱节点
private Node findPreNode(Node node) {
if (null == node) return null;
if (null != node.left){
Node left = node.left;
while (null != left.right){
left = left.right;
}
return left;
}
return null;
}复制代码
查找后继节点
private Node findNextNode(Node node) {
if (null == node) return null;
if (null != node.right) {
Node right = node.right;
while (null != right.left) {
right = right.left;
}
return right;
}
return null;
}复制代码
实现二叉查找树前、中、后序以及层级遍历
前序遍历
public void preOrderTraversal(Node tree) {
if (null == tree) return;
System.out.print(tree.data + " ");
preOrderTraversal(tree.left);
preOrderTraversal(tree.right);
}复制代码
中序遍历
public void inOrderTraversal(Node tree) {
if (null == tree) return;
inOrderTraversal(tree.left);
System.out.print(tree.data + " ");
inOrderTraversal(tree.right);
}复制代码
后序遍历
public void postOrderTraversal(Node tree) {
if (null == tree) return;
postOrderTraversal(tree.left);
postOrderTraversal(tree.right);
System.out.print(tree.data + " ");
}复制代码
层级遍历
层级遍历的思想是维护一个队里,每一次遍历tree,将tree的节点打印,然后将tree的left和right节点存入队列里(每一次打印时从队列里取即可)。
public void levelTraversal(Node tree) {
if (null == tree) return;
LinkedList linkedList = new LinkedList<>();
Node node;
linkedList.offer(tree);
while (!linkedList.isEmpty()) {
node = linkedList.poll();
System.out.print(node.data + " ");
if (null != node.left) {
linkedList.offer(node.left);
}
if (null != node.right) {
linkedList.offer(node.right);
}
}
}复制代码
AVL树
定义
AVL树是一种平衡二叉树。貌似就windows对进程地址空间的管理用到了AVL树。
- 左右子树的高度差小于等于 1。
- 其每一个子树均为平衡二叉树。
核心
AVL树核心在于左旋和右旋上,当出现不平衡的情况时,它会自动调整。
声明一个AVL树的Node类
在普通二叉树的基础上新增了几个属性
- balance 是平衡标识,它的区间是[-1,1]
- height 是该节点的高度,自上而下计算,从0开始
public class Node {
/**
* 数据
*/
int data;
/**
* 高度
*/
int height;
/**
* 平衡标识
*/
int balance;
/**
* 左子树
*/
Node left;
/**
* 右子树
*/
Node right;
/**
* 根
*/
Node parent;
/**
* 有参构造方法
*
* @param data 数据
* @param parent 父节点
*/
Node(int data, Node parent) {
this.data = data;
this.parent = parent;
}
}复制代码
插入
插入方法和常规插入的区别是需要将当前节点作为根节点插入,并且执行一下自平衡方法。
private void insert(int data) {
if (null == root) {
root = new Node(data, null);
} else {
Node node = root;
Node parent;
while (node.data != data) {
parent = node;
boolean goleft = node.data > data;
node = goleft ? node.left : node.right;
if (null == node) {
if (goleft) {
parent.left = new Node(data, parent);
} else {
parent.right = new Node(data, parent);
}
rebalance(parent);
break;
}
}
}
}复制代码
删除
这里是根据节点删除。和常规的删除相比,也是多了一步自平衡。
private void delete(Node node) {
if (null == node.left && null == node.right) {
if (null == node.parent) {
root = null;
} else {
Node parent = node.parent;
if (parent.left == node) {
parent.left = null;
} else {
parent.right = null;
}
rebalance(parent);
}
}
if (null != node.left) {
Node child = node.left;
while (null != child.right) {
child = child.right;
}
node.data = child.data;
delete(child);
} else {
Node child = node.right;
if (null != child) {
while (null != child.left) {
child = child.left;
}
node.data = child.data;
delete(child);
}
}
}复制代码
自平衡
有这么四种情况。
这里是否需要自平衡,是根据balance来判断的,当balance为-2的时候,(如果该节点的左子树的左子树的高度小于右子树高度,左旋,)右旋;当balance为2的时候,(如果该节点的右子树的右子树的高度小于左子树高度,右旋,)左旋;最后判断根节点是否为null,是则递归,否则赋值,停止递归;平衡完毕。
private void rebalance(Node node) {
setBalance(node);
if (node.balance == -2) {
if (getHeight(node.left.left) < getHeight(node.left.right)) {
node.left = rotateLeft(node.left);
}
node = rotateRight(node);
} else if (node.balance == 2) {
if (getHeight(node.right.right) < getHeight(node.right.left)) {
node.right = rotateRight(node.right);
}
node = rotateLeft(node);
}
if (null != node.parent) {
rebalance(node.parent);
} else {
root = node;
}
}复制代码
左右旋
左旋和右旋的原理是一样的,只要了解一种即可。
private Node rotateLeft(Node node) {
Node right = node.right;
right.parent = node.parent;
node.right = right.left;
if (null != node.right)
node.right.parent = node;
right.left = node;
node.parent = right;
if (null != right.parent) {
if (right.parent.right == node) {
right.parent.right = right;
} else {
right.parent.left = right;
}
}
setBalance(node, right);
return right;
}复制代码
private Node rotateRight(Node node) {
Node left = node.left;
left.parent = node.parent;
node.left = left.right;
if (null != node.left)
node.left.parent = node;
left.right = node;
node.parent = left;
if (null != left.parent) {
if (left.parent.right == node) {
left.parent.right = left;
} else {
left.parent.left = left;
}
}
setBalance(node, left);
return left;
}复制代码
设置平衡点
首先是重置高度,取左右子树高度的最大值加一作为该节点的高度。
其次是设置平衡点,用来判断该节点是否平衡
private void setBalance(Node... nodes) {
for (Node node : nodes) {
if (null != node) {
//重置高度
node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;
node.balance = getHeight(node.right) - getHeight(node.left);
}
}
}复制代码
获取树的高度
private int getHeight(Node node) {
if (null != node)
return node.height;
return -1;
}复制代码
测试代码
这里打印用的中序遍历(中序遍历可以从小到大打印出来)
private void print(Node node) {
if (null != node) {
print(node.left);
System.out.println(node.data + " " + node.balance);
print(node.right);
}
}
public static void main(String[] args) {
AVLTree avlTree = new AVLTree();
for (int i = 1; i < 10; i++) {
avlTree.insert(i);
}
avlTree.print();
}复制代码
测试结果
4
2 6
1 3 5 8
7 9
data - balance
end
您的点赞和关注是对我最大的支持,谢谢!