二叉排序树开端:
先来看一张二叉排序树的图:
![](https://img.haomeiwen.com/i11711317/58cab8f888c26144.png)
它或者是一颗空树,或者是一颗具有如下性质的树:
1)若左子树不为空,那么左子树上面的所有节点的关键字值都比根节点的关键字值小
2)若右子树不为空,那么右子树上面的所有节点的关键字值都比根节点的关键字值大
3)左右子树都为二叉树
4)没有重复值(这一点在实际中可以忽略)
下面咱们手动来根据上面的规则来画一张二叉排序树,先来对这个树的规则了解清楚,比如数列“5、2、7、3、4、1、6”,下面来让它生成一个二叉排序树:
先拿5作为根结点:
![](https://img.haomeiwen.com/i11711317/9f50da4a6e9fe2c4.png)
然后再拿2,由于它比5要小,所以放在5的左边:
![](https://img.haomeiwen.com/i11711317/2b1470097d711436.png)
再拿7,由于它比5要大,则放在它的右边:
![](https://img.haomeiwen.com/i11711317/ce9d8d3cf7d5cfd0.png)
接着再拿3,先跟5比,比它小则往它的左边找,此时则跟2进行对比,由于它比2要大,则放在2的右边,如下:
![](https://img.haomeiwen.com/i11711317/e33a5cdf140481f4.png)
再拿4,它比5小则往左找,到了2,则于它比2要大则继续往它的右边找,则到了3,由于它比3也要大,则将其放到3的右边,如下:
![](https://img.haomeiwen.com/i11711317/5bd3e284344bf08b.png)
接着再拿1,比5比2都要小,则放在2的左边,如下:
![](https://img.haomeiwen.com/i11711317/e04368712f5bed51.png)
最后6,由于它比5大,则往右找,由于它比7要小,则放到7的左边,最终整个二叉排序树就出来了:
![](https://img.haomeiwen.com/i11711317/8af99065afe49227.png)
好,如果对上面的树进行一下中序遍历(左中右)的话,其结果会让你吃惊:1、2、3、4、5、6、7,居然取出来的结果就变成有序的了。。
接下来咱们则用代码来生成上面的这棵树,我们之前在定义数的结构时也就是一个数据域,一个左结点,一个右结点,回忆一下:
![](https://img.haomeiwen.com/i11711317/09d0b40182b99a22.png)
但是!!此时需要增加一个信息了,也就它的父结点,为啥?这为之后的删除节点做准备,这里可以简单试想一下,假如我们要删除排序二叉树中的这样一个节点:
![](https://img.haomeiwen.com/i11711317/7a0c0c5e2c3c0bcd.png)
如果要删除的节点木有父节点的信息,请问下面的节点如何链接上它的父节点?所以咱们来定义一下:
![](https://img.haomeiwen.com/i11711317/c7703855efc59d5b.png)
主要操作:
添加节点:
首先定义一个添加方法:
![](https://img.haomeiwen.com/i11711317/1e634238276bef4e.png)
那接下添加肯定有比较大小的过程,首先如果没有根结点,则直接将要添加的数据作为根节点既可,如下:
![](https://img.haomeiwen.com/i11711317/971da9fafec47b0e.png)
接下来则是需要进行数据大小比较的,如果该数比节点小则往左找,比它大则往右找,代码如下:
![](https://img.haomeiwen.com/i11711317/edd57f492e6d0fd7.png)
当跳出循环则代表是已经找到待插入元素所存放的位置了,接下来则需要根据这个数据生成一个新节点将其插入到树当中,如下:
![](https://img.haomeiwen.com/i11711317/7decf7733f8ec75a.png)
遍历:
咱们直接用中序遍历既可,遍历出来就是一个有序数列:
![](https://img.haomeiwen.com/i11711317/5992f94ed89184bc.png)
好,接下来咱们来验证一下程序是否写得有问题:
![](https://img.haomeiwen.com/i11711317/17fc99cb57435514.png)
那如果有重复值呢?试一下:
![](https://img.haomeiwen.com/i11711317/e68901cea5442b80.png)
以上就构建了一个去重复值的排序二叉树。
查询节点:
查询就比较简单了,待查找的数比当前结点要小则往左找,则它大则往右找,如下:
![](https://img.haomeiwen.com/i11711317/1cbd9b8f86a696fe.png)
下面来调用一下:
![](https://img.haomeiwen.com/i11711317/36e5bdcdc7a542d8.png)
嗯~~确实是木问题,接下来咱们来找一个找不到的值:
![](https://img.haomeiwen.com/i11711317/8de46c0b5ebb2b84.png)
删除节点:
接下来最最复杂的操作来了,删除节点。。为啥难呢?下面写着就知道了,总之它有四种情况,下面一个个情况来逐步处理:
①、要删除的节点是叶子节点:比如:
![](https://img.haomeiwen.com/i11711317/fa9110d4e0b72279.png)
这种情况处理比较简单,只要让父结点的子结点为null既可,如下:
![](https://img.haomeiwen.com/i11711317/8ac91d8b978c8b02.png)
先把整个框架条件定好,接下来先处理第一种情况,先来处理一下特殊情况:如果整棵树就只有一个结点:
![](https://img.haomeiwen.com/i11711317/58667ee346afdc60.png)
其也满足叶子结点的条件,所以可以这样处理:
![](https://img.haomeiwen.com/i11711317/04fcb2a4b5b880b3.png)
然后再来处理正常叶子的情况:
![](https://img.haomeiwen.com/i11711317/4243a72ac91b8365.png)
②、要删除的结点它只有左孩子,比如:
![](https://img.haomeiwen.com/i11711317/a55207f19bb252ec.png)
也就是再来处理这个条件分支:
![](https://img.haomeiwen.com/i11711317/26e80e997c329618.png)
其思路就是需要将要删除结点的左孩子往上顶替回来,也就是说:
![](https://img.haomeiwen.com/i11711317/75921ff21acbae22.png)
所以看一下具体实现,所先判断是否删的是根结点:
![](https://img.haomeiwen.com/i11711317/f2dc8aacdcb95d1a.png)
接下来else的条件则是说明要删除的结点不是根结点了,但是此时还存在两种情况,一种是在父节点左边,另一种是在父节点右边,如下:
![](https://img.haomeiwen.com/i11711317/a342e7ea1785f4ff.png)
所以代码条件框架如下:
![](https://img.haomeiwen.com/i11711317/daa8214529b64181.png)
处理也比较简单,直接将要删除结点的左孩子接到父节点上既可,如下:
![](https://img.haomeiwen.com/i11711317/8b7104735c42bb11.png)
③、要删除的结点只有右孩子,跟情况2类似。所以直接依葫芦画瓢既可,只是将左孩子统一换成右孩子既可,如下:
/**
* 删除节点
*/
public void deleteNode(TreeNode node) {
if (node == null) {
throw new NoSuchElementException();
} else {
//先得到父亲,方便后面的操作
TreeNode parent = node.parent;
if (node.left == null && node.right == null) {
//1、node是个叶子
if (parent == null)//如果要删除的结点就是整棵树的唯一结点,则将root置为Null,因为删掉之后就空树了嘛
root = null;
else if (parent.right == node) {
parent.right = null;//直接将右结点置为null
} else if (parent.left == node) {
parent.left = null;//直接将左结点置为null
}
node.parent = null;
} else if (node.left != null && node.right == null) {
//2、只有左孩子
if (parent == null) {
//说明删除的结点是根结点,直接将子结点接上来
node.parent = null;
node.left.parent = null;//将它的左孩子作为根结点
root = node.left;//重新更新根结点
} else {
if (parent.left == node) {
//要删除的节点是在父亲的左边
parent.left = node.left;
} else if (parent.right == node) {
//要删除的节点是在父亲的右边
parent.right = node.left;
}
node.parent = null;
}
} else if (node.left == null && node.right != null) {
//3、只有右孩子
if (parent == null) {
//说明删除的结点是根结点,直接将子结点接上来
node.parent = null;
node.right.parent = null;//将它的左孩子作为根结点
root = node.right;//重新更新根结点
} else {
if (parent.left == node) {
//要删除的节点是在父亲的左边
parent.left = node.right;
} else if (parent.right == node) {
//要删除的节点是在父亲的右边
parent.right = node.right;
}
node.parent = null;
}
} else if (node.left != null && node.right != null) {
//4、有左右孩子
//TODO
}
}
}
[![image.gif](https://img.haomeiwen.com/i11711317/80a915bbd390ce84.gif?imageMogr2/auto-orient/strip)](file:///Applications/%E5%8D%B0%E8%B1%A1%E7%AC%94%E8%AE%B0.app/Contents/Resources/common-editor-mac/mac.html# "复制代码")
其实代码此时已经有bug了,修改如下:
[![image.gif](https://img.haomeiwen.com/i11711317/0970fcf35a69aa5a.gif?imageMogr2/auto-orient/strip)](file:///Applications/%E5%8D%B0%E8%B1%A1%E7%AC%94%E8%AE%B0.app/Contents/Resources/common-editor-mac/mac.html# "复制代码")
/**
* 删除节点
*/
public void deleteNode(TreeNode node) {
if (node == null) {
throw new NoSuchElementException();
} else {
//先得到父亲,方便后面的操作
TreeNode parent = node.parent;
if (node.left == null && node.right == null) {
//1、node是个叶子
if (parent == null)//如果要删除的结点就是整棵树的唯一结点,则将root置为Null,因为删掉之后就空树了嘛
root = null;
else if (parent.right == node) {
parent.right = null;//直接将右结点置为null
} else if (parent.left == node) {
parent.left = null;//直接将左结点置为null
}
node.parent = null;
} else if (node.left != null && node.right == null) {
//2、只有左孩子
if (parent == null) {
//说明删除的结点是根结点,直接将子结点接上来
node.parent = null;
node.left.parent = null;//将它的左孩子作为根结点
root = node.left;//重新更新根结点
} else {
if (parent.left == node) {
//要删除的节点是在父亲的左边
node.left.parent = parent;
parent.left = node.left;
} else if (parent.right == node) {
//要删除的节点是在父亲的右边
node.left.parent = parent;
parent.right = node.left;
}
node.parent = null;
}
} else if (node.left == null && node.right != null) {
//3、只有右孩子
if (parent == null) {
//说明删除的结点是根结点,直接将子结点接上来
node.parent = null;
node.right.parent = null;//将它的左孩子作为根结点
root = node.right;//重新更新根结点
} else {
if (parent.left == node) {
//要删除的节点是在父亲的左边
node.right.parent = parent;
parent.left = node.right;
} else if (parent.right == node) {
//要删除的节点是在父亲的右边
node.right.parent = parent;
parent.right = node.right;
}
node.parent = null;
}
} else if (node.left != null && node.right != null) {
//4、有左右孩子
//TODO
}
}
}
④、要删除的结点既有左孩子,又有右孩子。比如:
![](https://img.haomeiwen.com/i11711317/1413df55fc2f10a6.png)
也就是对应这个条件的代码:
![](https://img.haomeiwen.com/i11711317/97fffe779d128f30.png)
此时最复杂的操作来了,下面得留个心,很容易写错的,因为涉及到的情况也挺多的,下面一个个情况进行分析然后进行一一处理:
情况一:
![](https://img.haomeiwen.com/i11711317/88b326e6e8fe3c2d.png)
那此时删掉之后,得由7往上补,因为都了右结点,都比左边的数要大,所以最终形态会是:
![](https://img.haomeiwen.com/i11711317/44597d06b6170e37.png)
那下面用代码来处理这种情况:
![](https://img.haomeiwen.com/i11711317/8dbc409ca5f10296.png)
这里还有一种情况如下:
![](https://img.haomeiwen.com/i11711317/d99d8365279de40e.png)
也就是要删除的节点不是根节点,那此时则直接将7往上替换既可,如下:
![](https://img.haomeiwen.com/i11711317/cdc848600dd36b5f.png)
所以这块逻辑的代码比较简单:
![](https://img.haomeiwen.com/i11711317/02f4d8fcdea6fab0.png)
类似的,还有可能在根结点左边,如下:
![](https://img.haomeiwen.com/i11711317/141e48088a5db68e.png)
像这种处理也是类似,直接将7顶替上来既可:
![](https://img.haomeiwen.com/i11711317/43e3827f98791858.png)
所以相似的处理代码:
![](https://img.haomeiwen.com/i11711317/516813308ea1d86d.png)
情况二:
![](https://img.haomeiwen.com/i11711317/84924a0ddb5e9ee9.png)
此时就要注意啦,就不能直接将7给被上去了啦,为啥,假如把7替上去,就不符合排序二叉树了?所以应该是遍历它的左孩子将其最小的4顶上去,如下:
![](https://img.haomeiwen.com/i11711317/f0303df317de1ea4.png)
所以下面来实现一下,先来封装一下找节点最小值的方法:
![](https://img.haomeiwen.com/i11711317/ccc6696817b26c60.png)
此时也就是找到了这个结点了:
![](https://img.haomeiwen.com/i11711317/3a35aa54043dffb2.png)
接下来则需要将这个4放到5的位置,如何做呢?先这样做:
![](https://img.haomeiwen.com/i11711317/fd7b7a30ab747cbc.png)
所以先处理这种情况,得一个个来,不然很容易绕晕的,而且必须要画图,具体代码如下:
![](https://img.haomeiwen.com/i11711317/0f093b37e89a9cdf.png)
此时的形态就变为:
![](https://img.haomeiwen.com/i11711317/d107469f28f68269.png)
好,接下来需要对最小节点做引用处理,因为有可能在它下面还有树,比如:
![](https://img.haomeiwen.com/i11711317/0028dff55aa809b7.png)
此时则需要将4这棵最小的值的右子树得先处理好了,才能将4作为根节点,所以代码处理如下:
![](https://img.haomeiwen.com/i11711317/8578f9e5e435486e.png)
所以此时的形态就变为:
![](https://img.haomeiwen.com/i11711317/57a67ef7f6adc04d.png)
注意需要将4的父结点的引用给去掉,以便将4顶上去,所以修改一下代码:
![](https://img.haomeiwen.com/i11711317/8ce68b91043dff9f.png)
好,接下来再处理第三步,则应该是将7作为4的右子树,形态就会变为:
![](https://img.haomeiwen.com/i11711317/da79536c4a3a6fe6.png)
所以这一步的代码为:
![](https://img.haomeiwen.com/i11711317/650fe6b6b8ab9a6f.png)
同样的它也有一种特殊情况,比如要删的这个节点不是根结点,如下:
![](https://img.haomeiwen.com/i11711317/32578090a3b111e7.png)
此时直接将4接到根结点既可,如下:
![](https://img.haomeiwen.com/i11711317/636ad931454de44a.png)
所以代码如下:
![](https://img.haomeiwen.com/i11711317/9914da8a9e8242ba.png)
此时处理的是根结点的情况,也就是形态会成了这样了:
![](https://img.haomeiwen.com/i11711317/c3423d1c3f3de8a0.png)
接下来再来处理有父节点的:
![](https://img.haomeiwen.com/i11711317/026d3826df39ae5f.png)
有父节点的就有两种情况,一个是被删除的子节点位于父节点左边,一个是位于父节点右边,下面以位于父节点右边为例来看一下目前的形态:
![](https://img.haomeiwen.com/i11711317/b632a8c0506494a6.png)
于是乎,整个删除节点的代码如下:
/**
* 删除节点
*/
public void deleteNode(TreeNode node) {
if (node == null) {
throw new NoSuchElementException();
} else {
//先得到父亲,方便后面的操作
TreeNode parent = node.parent;
if (node.left == null && node.right == null) {
//1、node是个叶子
if (parent == null)//如果要删除的结点就是整棵树的唯一结点,则将root置为Null,因为删掉之后就空树了嘛
root = null;
else if (parent.right == node) {
parent.right = null;//直接将右结点置为null
} else if (parent.left == node) {
parent.left = null;//直接将左结点置为null
}
node.parent = null;
} else if (node.left != null && node.right == null) {
//2、只有左孩子
if (parent == null) {
//说明删除的结点是根结点,直接将子结点接上来
node.parent = null;
node.left.parent = null;//将它的左孩子作为根结点
root = node.left;//重新更新根结点
} else {
if (parent.left == node) {
//要删除的节点是在父亲的左边
node.left.parent = parent;
parent.left = node.left;
} else if (parent.right == node) {
//要删除的节点是在父亲的右边
node.left.parent = parent;
parent.right = node.left;
}
node.parent = null;
}
} else if (node.left == null && node.right != null) {
//3、只有右孩子
if (parent == null) {
//说明删除的结点是根结点,直接将子结点接上来
node.parent = null;
node.right.parent = null;//将它的左孩子作为根结点
root = node.right;//重新更新根结点
} else {
if (parent.left == node) {
//要删除的节点是在父亲的左边
node.right.parent = parent;
parent.left = node.right;
} else if (parent.right == node) {
//要删除的节点是在父亲的右边
node.right.parent = parent;
parent.right = node.right;
}
node.parent = null;
}
} else if (node.left != null && node.right != null) {
//4、有左右孩子
if (node.right.left == null) {
//如果被删除的右子树的左子树为空,则直接补上右子树
node.right.left = node.left;
if (parent == null) {
//如果要删的根结点
root = node.right;
} else {
//如果不是根结点
if (parent.right == node) {
parent.right = node.right;
} else {
parent.left = node.right;
}
}
node.left = null;
node.right = null;
node.parent = null;
} else {
//需要补上右子树的左子树上最小的一位
TreeNode treeNode = getMinLeftTreeNode(node.right);
treeNode.left = node.left;
//将最小左子树结点的右子树全部接上来
TreeNode leftNodeParent = treeNode.parent;
leftNodeParent.left = treeNode.right;
treeNode.right.parent = leftNodeParent;
treeNode.parent = null;
//将最小左子树结点顶到根位置
treeNode.right = node.right;
//处理根情况
if (parent == null) {
//要删除的节点既为根节点
node.left = null;
node.right = null;
root = leftNodeParent;
} else {
//要删除的节点不是根节点
if (parent.left == node) {
//左节点
parent.left = treeNode;
} else {
//右节点
parent.right = treeNode;
}
treeNode.parent = parent;
node.left = null;
node.right = null;
node.parent = null;
}
}
}
}
}
真的是。。晕菜了~~太TM复杂了,下面先来论证一下是否写得靠谱:
![](https://img.haomeiwen.com/i11711317/a45a81daa216db16.png)
真是实力打脸,得找bug了。。
![](https://img.haomeiwen.com/i11711317/5de7b5bbcab4a49e.png)
再运行:
![](https://img.haomeiwen.com/i11711317/b23bd5c7d48f143d.png)
嗯,对了,再来测:
![](https://img.haomeiwen.com/i11711317/69bd8ebee05948e4.png)
继续找bug:
![](https://img.haomeiwen.com/i11711317/5cce792f4f7e1a8f.png)
![](https://img.haomeiwen.com/i11711317/c32355fc8451f29b.png)
下面看一下为啥输出不对了,调一下:
![](https://img.haomeiwen.com/i11711317/41312151c0c82c92.png)
另外,在删2时,发现它的parent还是5,不太对,所以得继续找原因,发现是这块的问题:
![](https://img.haomeiwen.com/i11711317/7949a713871bf812.png)
再运行:
![](https://img.haomeiwen.com/i11711317/7a42f805d659ccdb.png)
正确了,可见这块删除只要有一个关系没处理好其结果就会有问题,面试要谁问到手写它,我觉得没个几个小时肯定是很难写得非常正确的,好下面继续再测试:
![](https://img.haomeiwen.com/i11711317/91dd17d73681f504.png)
看一下结果:
![](https://img.haomeiwen.com/i11711317/4253979a58dba7c3.png)
哇塞,真不容易~~完美!!最后代码定格在:
package com.datastruct.test.tree;
import java.util.NoSuchElementException;
public class SearchBinaryTree {
//根节点
TreeNode root;
public TreeNode put(int data) {
if (root == null) {//如果没有根节点,则直接将其作为根节点
TreeNode node = new TreeNode(data);
root = node;
return root;
}
TreeNode parent = null;
TreeNode node = root;
while (node != null) {
parent = node;//再往下移之前需要记录一下父元素,以便在找到要插入的位置时将其进行连接上
if (data < node.data) {
//则往左找
node = node.left;
} else if (data > node.data) {
//则往右找
node = node.right;
} else {
//如果已经有相同的重复节点了,则直接返回,因为二叉排序树是不允许重复的
return node;
}
}
//生成一个新节点将其插入
TreeNode newNode = new TreeNode(data);
if (data < parent.data) {
parent.left = newNode;
} else {
parent.right = newNode;
}
newNode.parent = parent;
return newNode;
}
/**
* 中序遍历
*/
public void midOrderTraverse(TreeNode root) {
if (root == null)
return;
midOrderTraverse(root.left);
System.out.print(root.data + " ");
midOrderTraverse(root.right);
}
/**
* 查找接点
*/
public TreeNode searchNode(int data) {
if (root == null)
return null;
TreeNode node = root;
while (node != null) {
if (node.data == data)
return node;
else if (data < node.data) {
//则往左找
node = node.left;
} else if (data > node.data) {
//则往右找
node = node.right;
}
}
return null;
}
/**
* 删除节点
*/
public void deleteNode(TreeNode node) {
if (node == null) {
throw new NoSuchElementException();
} else {
//先得到父亲,方便后面的操作
TreeNode parent = node.parent;
if (node.left == null && node.right == null) {
//1、node是个叶子
if (parent == null)//如果要删除的结点就是整棵树的唯一结点,则将root置为Null,因为删掉之后就空树了嘛
root = null;
else if (parent.right == node) {
parent.right = null;//直接将右结点置为null
} else if (parent.left == node) {
parent.left = null;//直接将左结点置为null
}
node.parent = null;
} else if (node.left != null && node.right == null) {
//2、只有左孩子
if (parent == null) {
//说明删除的结点是根结点,直接将子结点接上来
node.parent = null;
node.left.parent = null;//将它的左孩子作为根结点
root = node.left;//重新更新根结点
} else {
if (parent.left == node) {
//要删除的节点是在父亲的左边
node.left.parent = parent;
parent.left = node.left;
} else if (parent.right == node) {
//要删除的节点是在父亲的右边
node.left.parent = parent;
parent.right = node.left;
}
node.parent = null;
}
} else if (node.left == null && node.right != null) {
//3、只有右孩子
if (parent == null) {
//说明删除的结点是根结点,直接将子结点接上来
node.parent = null;
node.right.parent = null;//将它的左孩子作为根结点
root = node.right;//重新更新根结点
} else {
if (parent.left == node) {
//要删除的节点是在父亲的左边
node.right.parent = parent;
parent.left = node.right;
} else if (parent.right == node) {
//要删除的节点是在父亲的右边
node.right.parent = parent;
parent.right = node.right;
}
node.parent = null;
}
} else if (node.left != null && node.right != null) {
//4、有左右孩子
if (node.right.left == null) {
//如果被删除的右子树的左子树为空,则直接补上右子树
node.right.left = node.left;
if (parent == null) {
//如果要删的根结点
node.right.parent = null;
node.right.left.parent = node.right;
root = node.right;
} else {
//如果不是根结点
if (parent.right == node) {
parent.right = node.right;
} else {
parent.left = node.right;
}
node.right.parent = parent;
node.left.parent = node.right;
}
node.left = null;
node.right = null;
node.parent = null;
} else {
//需要补上右子树的左子树上最小的一位
TreeNode treeNode = getMinLeftTreeNode(node.right);
treeNode.left = node.left;
node.left.parent = treeNode;
//将最小左子树结点的右子树全部接上来
TreeNode leftNodeParent = treeNode.parent;
leftNodeParent.left = treeNode.right;
if (treeNode.right != null)
treeNode.right.parent = leftNodeParent;
treeNode.parent = null;
//将最小左子树结点顶到根位置
treeNode.right = node.right;
//处理根情况
if (parent == null) {
//要删除的节点既为根节点
node.left = null;
node.right = null;
leftNodeParent.parent = treeNode;
root = treeNode;
} else {
//要删除的节点不是根节点
if (parent.left == node) {
//左节点
parent.left = treeNode;
} else {
//右节点
parent.right = treeNode;
}
treeNode.parent = parent;
node.left = null;
node.right = null;
node.parent = null;
}
}
}
}
}
/**
* 找左节点最小值
*/
private TreeNode getMinLeftTreeNode(TreeNode node) {
TreeNode curNode = null;
if (node == null)
return null;
else {
curNode = node;
while (curNode.left != null) {
curNode = curNode.left;
}
}
return curNode;
}
public class TreeNode {
int data;
TreeNode left;
TreeNode right;
TreeNode parent;
public TreeNode(int data) {
this.data = data;
this.left = null;
this.right = null;
this.parent = null;
}
}
}
网友评论