二叉树结构
在进行链表结构开发的过程之中会发现所有的数据按照首尾相连的状态进行保存,那么当要进行某一个数据查询的时候(判断该数据是否在此链表中存在)买这种情况下查询的时间复杂度是“O(n)”(有多少数据就得查多少遍)。如果数据量小(不超过30个),那么性能差异不大,一旦保存的数据很大,这时候的时间复杂度就会严重损耗程序的运行性能。那么现在对于数据的存储结构就会发生改变,需要一种尽量减少检索次数的机构!对于此种数据结构而言,最好的性能就是“O(logn)”。实现此种数据结构可以利用二叉树来完成。
二叉树的插入和遍历
二叉树的数据插入
数据的插入,核心就是当前插入节点于二叉树中的节点进行比较(通过比较器比较),直到找到一个合适的位置,然后插入进去
代码实现(核心逻辑):
/**
* 拿当前节点和二叉树进行比较,查找插入的位置
*
* @param t
*/
public void add(T t) {
if (Objects.isNull(t)) {
throw new NullPointerException("数据不能为null");
}
Node node = new Node(t);
// 如果当前树中没有数据,直接保存当前节点为根节点
if (Objects.isNull(this.root)) {
this.root = node;
} else {
// 将数据插入到二叉树中
this.root.addNode(node);
}
this.count++;
}
二叉树的遍历
树的遍历有三种方式:前序遍历(中,左,右)、中序遍历(左,中,右)和后序遍历(左,右,中)。
二叉搜索树的数据删除
二叉树之中的数据删除操作是非常复杂的,因为在进行数据删除的时候需要考虑的情况是比较多的。
- 如果待删除节点没有子节点,那么直接删除掉即可。
- 如果待删除节点只有一个子节点,那么直接删除掉,并用其子节点去代替它。
- 如果待删除节点有两个子节点,这种情况比较复杂:首先找出它的后继节点,然后处理“后继节点”和“被删除节点的父节点”之间的关系,最后处理“后继节点的子节点”和“被删除节点的子节点”之间的关系。
稍后代码里插入的数据会符合如下结构,就以此结构进行分析(注意红色的节点是不存在,是为了解释下面的情况二准备的)
先看情况一:删除30,50,80,90这种节点,直接删除即可。但是代码实现的时候,需要考虑当前节点是其父节点的左节点还是右节点。这点很重要!否则逻辑上就出现BUG了,这点我在下边的案例上实现了。
情况二:删除60,85这种节点,也是上边的逻辑,但是在代码实现的也要考虑多种情况,如上图中两个红色节点(我没有实现这两种数据的删除),也即是说如果这两个节点都存在,那么要想删除30,60,80,85这四个节点的情况都是不同的。需要都实现,否则会出现程序漏洞。【我在下面的案例中只实现了非染色的这两种情况】
情况三:删除根节点。这种情况的处理方式,与上边分析的第三种情况分析较为类似。具体可以参考下面代码(有注释)。
删除代码的核心逻辑:
/**
* 删除二叉树中的节点
* 注意:在进行删除时,节点直接的关联操作都是考虑双向的,一定要记住这一点,否则会有点迷
*/
public void remove(T data) {
// 二叉树没有节点
if (Objects.isNull(this.root)) {
return;
} else {
// 删除的元素是根节点
if (this.root.data.compareTo(data) == 0) {
Node moveNode = this.root.right;
while (Objects.nonNull(moveNode.left)) {
// 一直找左边的节点
moveNode = moveNode.left;
}
moveNode.left = this.root.left;
moveNode.right = this.root.right;
moveNode.parent.left = null;
// 设置移动节点为根节点
this.root = moveNode;
// 删除元素,树的节点个数减一
this.count--;
// 不是根节点
} else {
Node removeNode = this.root.getRemoveNode(data);
if (Objects.nonNull(removeNode)) {
// 情况一:被删除节点没有任何子节点
if (Objects.isNull(removeNode.left) && Objects.isNull(removeNode.right)) {
// 说明当前删除节点是其父亲节点的左子节点
if (Objects.nonNull(removeNode.parent.left) && removeNode.data.compareTo(removeNode.parent.left.data) == 0) {
removeNode.parent.left = null;
} else {
removeNode.parent.right = null;
}
// 断掉删除节点与树的联系
removeNode.parent = null;
// 情况二:左边节点存在,右边节点不存在
} else if (Objects.isNull(removeNode.right) && Objects.nonNull(removeNode.left)) {
// 这种情况下,也会存在两种情况。
// 代码里就不实现了,稍后画图分析
removeNode.parent.right = removeNode.left;
removeNode.left.parent = removeNode.parent;
removeNode.parent = null;
// 情况三:右边节点存在,左边节点不存在
} else if (Objects.isNull(removeNode.left) && Objects.nonNull(removeNode.right)) {
// 注意,此时应该是删除节点的父节点的左边和删除节点的右边关联
removeNode.parent.right = removeNode.right;
// 删除节点的右边几点和删除节点的父节点的左边进行关联
removeNode.right.parent = removeNode.parent;
// 情况四:两边都有节点
} else {
// 将右边节点最左边节点找到,改变其引用
Node moveNode = removeNode.right;
while (Objects.nonNull(moveNode.left)) {
// 一直找左边的节点
moveNode = moveNode.left;
}
// 断开原本的连接
removeNode.parent.left = moveNode;
moveNode.parent.left = null;
moveNode.parent = removeNode.parent;
moveNode.right = removeNode.right;
moveNode.left = removeNode.left;
}
// 删除数据,树的节点数量减一
this.count--;
}
}
}
}
二叉树实现的全部代码:
https://github.com/chenfu1201/java/tree/master/src/chenfu/tree