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<Node> 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
您的点赞和关注是对我最大的支持,谢谢!