二叉排序树(查找树、搜索树)

二叉排序树开端:

先来看一张二叉排序树的图:

它或者是一颗空树,或者是一颗具有如下性质的树:

1)若左子树不为空,那么左子树上面的所有节点的关键字值都比根节点的关键字值小

2)若右子树不为空,那么右子树上面的所有节点的关键字值都比根节点的关键字值大

3)左右子树都为二叉树

4)没有重复值(这一点在实际中可以忽略)

下面咱们手动来根据上面的规则来画一张二叉排序树,先来对这个树的规则了解清楚,比如数列“5、2、7、3、4、1、6”,下面来让它生成一个二叉排序树:

先拿5作为根结点:

然后再拿2,由于它比5要小,所以放在5的左边:

再拿7,由于它比5要大,则放在它的右边:

接着再拿3,先跟5比,比它小则往它的左边找,此时则跟2进行对比,由于它比2要大,则放在2的右边,如下:

再拿4,它比5小则往左找,到了2,则于它比2要大则继续往它的右边找,则到了3,由于它比3也要大,则将其放到3的右边,如下:

接着再拿1,比5比2都要小,则放在2的左边,如下:

最后6,由于它比5大,则往右找,由于它比7要小,则放到7的左边,最终整个二叉排序树就出来了:

好,如果对上面的树进行一下中序遍历(左中右)的话,其结果会让你吃惊:1、2、3、4、5、6、7,居然取出来的结果就变成有序的了。。

接下来咱们则用代码来生成上面的这棵树,我们之前在定义数的结构时也就是一个数据域,一个左结点,一个右结点,回忆一下:

但是!!此时需要增加一个信息了,也就它的父结点,为啥?这为之后的删除节点做准备,这里可以简单试想一下,假如我们要删除排序二叉树中的这样一个节点:

如果要删除的节点木有父节点的信息,请问下面的节点如何链接上它的父节点?所以咱们来定义一下:

主要操作:

添加节点:

首先定义一个添加方法:

那接下添加肯定有比较大小的过程,首先如果没有根结点,则直接将要添加的数据作为根节点既可,如下:

接下来则是需要进行数据大小比较的,如果该数比节点小则往左找,比它大则往右找,代码如下:

当跳出循环则代表是已经找到待插入元素所存放的位置了,接下来则需要根据这个数据生成一个新节点将其插入到树当中,如下:

遍历:

咱们直接用中序遍历既可,遍历出来就是一个有序数列:

好,接下来咱们来验证一下程序是否写得有问题:

那如果有重复值呢?试一下:

以上就构建了一个去重复值的排序二叉树。

查询节点:

查询就比较简单了,待查找的数比当前结点要小则往左找,则它大则往右找,如下:

下面来调用一下:

嗯~~确实是木问题,接下来咱们来找一个找不到的值:

删除节点:

接下来最最复杂的操作来了,删除节点。。为啥难呢?下面写着就知道了,总之它有四种情况,下面一个个情况来逐步处理:

①、要删除的节点是叶子节点:比如:

这种情况处理比较简单,只要让父结点的子结点为null既可,如下:

先把整个框架条件定好,接下来先处理第一种情况,先来处理一下特殊情况:如果整棵树就只有一个结点:

其也满足叶子结点的条件,所以可以这样处理:

然后再来处理正常叶子的情况:

②、要删除的结点它只有左孩子,比如:

也就是再来处理这个条件分支:

其思路就是需要将要删除结点的左孩子往上顶替回来,也就是说:

所以看一下具体实现,所先判断是否删的是根结点:

接下来else的条件则是说明要删除的结点不是根结点了,但是此时还存在两种情况,一种是在父节点左边,另一种是在父节点右边,如下:

所以代码条件框架如下:

处理也比较简单,直接将要删除结点的左孩子接到父节点上既可,如下:

③、要删除的结点只有右孩子,跟情况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://upload-images.jianshu.io/upload_images/11711317-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://upload-images.jianshu.io/upload_images/11711317-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

}

}

}

④、要删除的结点既有左孩子,又有右孩子。比如:

也就是对应这个条件的代码:

此时最复杂的操作来了,下面得留个心,很容易写错的,因为涉及到的情况也挺多的,下面一个个情况进行分析然后进行一一处理:

情况一:

那此时删掉之后,得由7往上补,因为都了右结点,都比左边的数要大,所以最终形态会是:

那下面用代码来处理这种情况:

这里还有一种情况如下:

也就是要删除的节点不是根节点,那此时则直接将7往上替换既可,如下:

所以这块逻辑的代码比较简单:

类似的,还有可能在根结点左边,如下:

像这种处理也是类似,直接将7顶替上来既可:

所以相似的处理代码:

情况二:

此时就要注意啦,就不能直接将7给被上去了啦,为啥,假如把7替上去,就不符合排序二叉树了?所以应该是遍历它的左孩子将其最小的4顶上去,如下:

所以下面来实现一下,先来封装一下找节点最小值的方法:

此时也就是找到了这个结点了:

接下来则需要将这个4放到5的位置,如何做呢?先这样做:

所以先处理这种情况,得一个个来,不然很容易绕晕的,而且必须要画图,具体代码如下:

此时的形态就变为:

好,接下来需要对最小节点做引用处理,因为有可能在它下面还有树,比如:

此时则需要将4这棵最小的值的右子树得先处理好了,才能将4作为根节点,所以代码处理如下:

所以此时的形态就变为:

注意需要将4的父结点的引用给去掉,以便将4顶上去,所以修改一下代码:

好,接下来再处理第三步,则应该是将7作为4的右子树,形态就会变为:

所以这一步的代码为:

同样的它也有一种特殊情况,比如要删的这个节点不是根结点,如下:

此时直接将4接到根结点既可,如下:

所以代码如下:

此时处理的是根结点的情况,也就是形态会成了这样了:

接下来再来处理有父节点的:

有父节点的就有两种情况,一个是被删除的子节点位于父节点左边,一个是位于父节点右边,下面以位于父节点右边为例来看一下目前的形态:

于是乎,整个删除节点的代码如下:


/**

* 删除节点

*/

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复杂了,下面先来论证一下是否写得靠谱:

真是实力打脸,得找bug了。。

再运行:

嗯,对了,再来测:

继续找bug:

下面看一下为啥输出不对了,调一下:

另外,在删2时,发现它的parent还是5,不太对,所以得继续找原因,发现是这块的问题:

再运行:

正确了,可见这块删除只要有一个关系没处理好其结果就会有问题,面试要谁问到手写它,我觉得没个几个小时肯定是很难写得非常正确的,好下面继续再测试:

看一下结果:

哇塞,真不容易~~完美!!最后代码定格在:


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;

}

}

}

https://www.cnblogs.com/webor2006/p/11575167.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266