目录

  • 二叉查找树定义
  • 二叉查找树节点定义
  • 插入节点
  • 查找节点
    • 查找最小值
    • 查找最大值
    • 查找特定值
  • 删除节点
    • 删除叶子节点
    • 删除带有一个子节点的节点
    • 删除带有两个子节点的节点
    • 删除节点测试

二叉查找树定义

每棵子树头节点的值都比各自左子树上所有节点值要大,也都比各自右子树上所有节点值要小。

二叉查找树的中序遍历序列一定是从小到大排列的。

二叉查找树节点定义

/// <summary>
/// 二叉查找树节点
/// </summary>
public class Node
{/// <summary>/// 节点值/// </summary>public int Data { get; set; }/// <summary>/// 左子节点/// </summary>public Node Left { get; set; }/// <summary>/// 右子节点/// </summary>public Node Right { get; set; }/// <summary>/// 打印节点值/// </summary>public void DisplayNode(){Console.Write(Data + " ");}
}

插入节点

二叉查找树的插入节点操作相对比较简单,只需要找到要插入节点的位置放置即可。

插入节点的整体流程:

  1. 把父节点设置为当前节点,即根节点。
  2. 如果新节点内的数据值小于当前节点内的数据值,那么把当前节点设置为当前节点的左子节点。如果新节点内的数据值大于当前节点内的数据值,那么就跳到步骤 4。

  3. 如果当前节点的左子节点的数值为空(null),就把新节点插入在这里并且退出循环。否则,跳到 while 循环的下一次循环操作中。

  4. 把当前节点设置为当前节点的右子节点。
  5. 如果当前节点的右子节点的数值为空(null),就把新节点插入在这里并且退出循环。否则,跳到 while 循环的下一次循环操作中。

代码实现:

public class BinarySearchTree
{public Node root;public BinarySearchTree(){root = null;}/// <summary>/// 二叉查找树插入结点/// </summary>/// <param name="i"></param>public void Insert(int i){Node newNode = new Node{Data = i};if (root == null){root = newNode;}else{Node current = root;Node parent;while (true){parent = current;if (i < current.Data){current = current.Left;if (current == null){parent.Left = newNode;break;}}else{current = current.Right;if (current == null){parent.Right = newNode;break;}}}}}
}

因为二叉查找树的中序遍历序列一定是由小到大排列的,所以我们可以通过中序遍历测试二叉查找树的插入操作。关于二叉树遍历操作可以移步我的上一篇博客【图解数据结构】 二叉树遍历。

中序遍历代码实现:

/// <summary>
/// 二叉查找树中序遍历
/// </summary>
/// <param name="node"></param>
public void InOrder(Node node)
{if (node != null){InOrder(node.Left);node.DisplayNode();InOrder(node.Right);}
}

测试代码:

class BinarySearchTreeTest
{static void Main(string[] args){BinarySearchTree bst = new BinarySearchTree();bst.Insert(23);bst.Insert(45);bst.Insert(16);bst.Insert(37);bst.Insert(3);bst.Insert(99);bst.Insert(22);         Console.WriteLine("中序遍历: ");bst.InOrder(bst.root);Console.ReadKey();}
}

测试结果:

上面的测试代码形成了一棵这样的二叉查找树:

查找节点

对于 二叉查找树(BST) 有三件最容易做的事情:查找一个特殊数值,找到最小值,以及找到最大值。

查找最小值

根据二叉查找树的性质,二叉查找树的最小值一定是在左子树的最左侧子节点。

所以实现很简单,就是从根结点出发找出二叉查找树左子树的最左侧子节点。

代码实现:

/// <summary>
/// 查找二叉查找树最小值
/// </summary>
/// <returns></returns>
public int FindMin()
{Node current = root;while (current.Left != null){current = current.Left;}return current.Data;
}

查找最大值

根据二叉查找树的性质,二叉查找树的最大值一定是在右子树的最右侧子节点。

所以实现很简单,就是从根结点出发找出二叉查找树右子树的最右侧子节点。

代码实现:

/// <summary>
/// 查找二叉查找树最大值
/// </summary>
/// <returns></returns>
public int FindMax()
{Node current = root;while (current.Right != null){current = current.Right;}return current.Data;
}

查找特定值

根据二叉查找树的性质,从根结点开始,比较特定值和根结点值的大小。如果比根结点值大,则说明特定值在根结点右子树上,继续在右子节点执行此操作;如果比根结点值小,则说明特定值在根结点左子树上,继续在左子节点执行此操作。如果到执行完成都没有找到和特定值相等的节点值,那么二叉查找树中没有包含此特定值的节点。

代码实现:

/// <summary>
/// 查找二叉查找树特定值节点
/// </summary>
/// <param name="key">特定值</param>
/// <returns></returns>
public Node Find(int key)
{Node current = root;while (current.Data != key){if (key < current.Data){current = current.Left;}if (key > current.Data){current = current.Right;}// 如果已到达 BST 的末尾if (current == null){return null;}}return current;
}

删除节点

相对于前面的操作,二叉查找树的删除节点操作就显得要复杂一些了,因为删除节点会有破坏 BST 正确
层次顺序的风险。

我们都知道在二叉查找树中的结点可分为:没有子节点的节点,带有一个子节点的节点 ,带有两个子节点的节点 。那么可以将二叉查找树的删除节点操作简单拆分一下,以便于我们的理解。如下图:

删除叶子节点

删除叶子节点是最简单的事情。 唯一要做的就是把目标节点的父节点的一个子节点设置为空(null)。

查看这个节点的左子节点和右子节点是否为空(null),都为空(null)说明为叶子节点。

然后检测这个节点是否是根节点。如果是,就把它设置为空(null)。

否则,如果isLeftChild 为true,把父节点的左子节点设置为空(null);如果isLeftChild 为false,把父节点的右子节点设置为空(null)。

代码实现:

//要删除的结点是叶子结点的处理
if (current.Left == null && current.Right == null)
{if (current == root)root = null;else if (isLeftChild)parent.Left = null;else{parent.Right = null;}
}

删除带有一个子节点的节点

当要删除的节点有一个子节点的时候,需要检查四个条件:

  1. 这个节点的子节点可能是左子节点;
  2. 这个节点的子节点可能是右子节点;
  3. 要删除的这个节点可能是左子节点;
  4. 要删除的这个节点可能是右子节点。

代码实现:

//要删除的结点是带有一个子节点的节点的处理
//首先判断子结点是左子节点还是右子节点,然后再判断当前节点是左子节点还是右子节点
else if (current.Right == null)if (current == root)root = current.Left;else if (isLeftChild)parent.Left = current.Left;elseparent.Right = current.Left;
else if (current.Left == null)if (current == root)root = current.Right;else if (isLeftChild)parent.Left = current.Right;elseparent.Right = current.Right;

删除带有两个子节点的节点

如果要删除标记为 52 的节点,需要重构这棵树。这里不能用起始节点为 54 的子树来替换它,因为 54 已经有一个左子节点了。这个问题的答案是把中序后继节点移动到要删除节点的位置上。 当然还要区分后继节点本身是否有子节点。

这里我们需要了解一下后继节点的定义。

一个节点的后继节点是指,这个节点在中序遍历序列中的下一个节点。相应的,前驱节点是指这个节点在中序遍历序列中的上一个节点。

举个例子,下图中的二叉树中序遍历序列为: DBEAFCG,则A的后继节点为F,A的前驱节点为E。

了解了这些,删除带有两个子节点的节点的操作就可以转化为寻找要删除节点的后继节点并且把要删除节点的右子树赋给后继结点的右子节点,这里需要注意的是如果后继节点本身有子节点,则需要将后继节点的子结点赋给后继节点父节点的左子节点。

先上获取后继结点的代码,然后举个例子说明:

/// <summary>
/// 获取后继结点
/// </summary>
/// <param name="delNode">要删除的结点</param>
/// <returns></returns>
public Node GetSuccessor(Node delNode)
{//后继节点的父节点Node successorParent = delNode;//后继节点Node successor = delNode.Right;Node current = delNode.Right.Left;while (current != null){successorParent = successor;successor = current;current = current.Left;}//如果后继结点不是要删除结点的右子结点,//则要将后继节点的子结点赋给后继节点父节点的左节点//删除结点的右子结点赋给后继结点作为 后继结点的后继结点if (successor != delNode.Right){successorParent.Left = successor.Right;successor.Right = delNode.Right;}return successor;
}

删除带有两个子节点的节点的代码实现:

//要删除的结点是带有两个子节点的节点的处理
else
{Node successor = GetSuccessor(current);if (current == root)root = successor;else if (isLeftChild)parent.Left = successor;elseparent.Right = successor;//因为后继结点是要删除结点右子树的最左侧结点//所以后继结点的左子树肯定是要删除结点左子树successor.Left = current.Left;
}

我们观察到删除节点的后继节点一定是删除节点右子树的最左侧节点。这里有3种情况:

后继节点是删除节点的子节点

删除节点37,后继节点40是删除节点37的子节点。delNode是结点37,successor是节点40,delNode.Right是节点40,successor == delNode.Right,后继节点为删除节点的子节点,这种情况是最简单的。

后继节点不是删除节点的子节点

后继节点38是删除节点37右子树的最左侧节点。delNode是节点37,successor是节点38,successorParent 是节点40,delNode.Right 是节点40。successor != delNode.Right,所以要将 successorParent.Left = successor.Right;successor.Right = delNode.Right;。因为successor.Right==null,所以successorParent.Left = nullsuccessor.Right = delNode.Right,节点40成为了节点38的右子节点。因为删除节点的后继节点一定是删除节点右子树的最左侧节点,所以后继节点肯定没有左子节点。删除节点被删除后,后继结点会补到删除节点的位置。successor.Left = current.Left;,也就是删除节点的左子节点变成了后继节点的左子节点。

完成删除节点后的搜索二叉树变为:

后继节点不是删除节点的子节点且有子节点

这种情况和上一种情况相似,唯一的区别是后继节点有子节点(注意肯定是右子节点)。也就是successorParent.Left = successor.Right;,后继节点的右子节点变成后继结点父节点的左子节点。因为successor.Right是节点39,所以节点40的左子节点变成了节点39。其它操作和上一种情况完全相同。

完成删除节点后的搜索二叉树变为:

删除节点操作的整体流程:

  1. 把后继节点的右子节点赋值为后继节点的父节点的左子节点。
  2. 把要删除节点的右子节点赋值为后继节点的右子节点。
  3. 从父节点的右子节点中移除当前节点,并且把它指向后继节点。
  4. 从当前节点中移除当前节点的左子节点,并且把它指向后继节点的左子节点。

综合以上删除节点的三种情况,删除节点操作的完整代码如下:

/// <summary>
/// 二叉查找树删除节点
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public bool Delete(int key)
{//要删除的当前结点Node current = root;//当前结点的父结点Node parent = root;//当前结点是否是左子树 bool isLeftChild = true;//先通过二分查找找出要删除的结点while (current.Data != key){parent = current;if (key < current.Data){isLeftChild = true;current = current.Left;}else{isLeftChild = false;current = current.Right;}if (current == null)return false;}//要删除的结点是叶子结点的处理if (current.Left == null && current.Right == null){if (current == root)root = null;else if (isLeftChild)parent.Left = null;else{parent.Right = null;}}//要删除的结点是带有一个子节点的节点的处理else if (current.Right == null)if (current == root)root = current.Left;else if (isLeftChild)parent.Left = current.Left;elseparent.Right = current.Left;else if (current.Left == null)if (current == root)root = current.Right;else if (isLeftChild)parent.Left = current.Right;elseparent.Right = current.Right;//要删除的结点是带有两个子节点的节点的处理else{Node successor = GetSuccessor(current);if (current == root)root = successor;else if (isLeftChild)parent.Left = successor;elseparent.Right = successor;//因为后继结点是要删除结点右子树的最左侧结点//所以后继结点的左子树肯定是要删除结点左子树successor.Left = current.Left;}return true;
}/// <summary>
/// 获取后继结点
/// </summary>
/// <param name="delNode">要删除的结点</param>
/// <returns></returns>
public Node GetSuccessor(Node delNode)
{//后继节点的父节点Node successorParent = delNode;//后继节点Node successor = delNode.Right;Node current = delNode.Right.Left;while (current != null){successorParent = successor;successor = current;current = current.Left;}//如果后继结点不是要删除结点的右子结点,//则要将后继节点的子结点赋给后继节点父节点的左节点//删除结点的右子结点赋给后继结点作为 后继结点的后继结点if (successor != delNode.Right){successorParent.Left = successor.Right;successor.Right = delNode.Right;}return successor;
}

删除节点测试

我们还是使用中序遍历进行测试,首先构造二叉查找树:

static void Main(string[] args)
{BinarySearchTree bst = new BinarySearchTree();bst.Insert(23);bst.Insert(45);bst.Insert(16);bst.Insert(37);bst.Insert(3);bst.Insert(99);bst.Insert(22);bst.Insert(40);bst.Insert(35);bst.Insert(38);bst.Insert(44);bst.Insert(39);
}          

构造出的二叉查找树:

测试分三种情况:

测试删除叶子节点

删除叶子节点39

Console.Write("删除节点前: ");
bst.InOrder(bst.root);bst.Delete(39);Console.Write("删除节点后: ");
bst.InOrder(bst.root);

测试结果:

测试删除带有一个子节点的节点

删除带有一个子节点的节点38

Console.Write("删除节点前: ");
bst.InOrder(bst.root);bst.Delete(38);Console.Write("删除节点后: ");
bst.InOrder(bst.root);

测试结果:

测试删除带有两个子节点的节点

删除带有两个子节点的节点37

Console.Write("删除节点前: ");
bst.InOrder(bst.root);bst.Delete(37);Console.Write("删除节点后: ");
bst.InOrder(bst.root);

测试结果:

参考:

《数据结构与算法 C#语言描述》

《大话数据结构》

《数据结构与算法分析 C语言描述》

五一大家都出去happy了,为什么我还要自己在家撸代码,是因为爱吗?是因为责任吗?都不是。是因为我的心里只有学习(其实是因为)。哈哈,提前祝大家五一快乐,吃好玩好!



作者:喜欢天黑却怕鬼


来源:http://songwenjie.cnblogs.com/

声明:本文为博主学习感悟总结,水平有限,如果不当,欢迎指正。如果您认为还不错,不妨点击一下下方的【推荐】按钮,谢谢支持。转载与引用请注明出处。

转载于:https://www.cnblogs.com/songwenjie/p/8973217.html

【图解数据结构】二叉查找树相关推荐

  1. 【关于封装的那些事】 缺失封装 【关于封装的那些事】 泄露的封装 【关于封装的那些事】 不充分的封装 【图解数据结构】二叉查找树 【图解数据结构】 二叉树遍历...

    [关于封装的那些事] 缺失封装 目录 - 缺失封装 为什么不能缺失封装? 缺失封装潜在的原因 未意识到关注点会不断变化 混合关注点 幼稚的设计决策 示例分析一 示例分析二 总结 缺失封装 没有将实现变 ...

  2. 图解数据结构 使用python_[java电子书] 图解数据结构-使用Python PDF 电子书 百度云 网盘下载_Java自学网...

    java自学网(www.javazx.com)-java论坛,java电子书推荐:< 图解数据结构--使用Python>% J. T6 H1 L: y! ~! T7 A6 F) E& ...

  3. 【图解数据结构】排序全面总结(一)

    目录 一.前言 学习目标: 二.基本概念 1.排序 2.排序方法的稳定性 3.内部和外部排序 三.插入类排序 1.直接插入排序 2.折半插入排序 3.希尔排序 四.交换类排序 1.冒泡排序 2.快速排 ...

  4. 【图解数据结构与算法】视频教程正式上线B站,持续更新中......

    本主[图解数据结构与算法(Java语言描述)] B站传送门 https://www.bilibili.com/video/BV1ea4y1e7v7/

  5. 【图解数据结构】树和二叉树全面总结(上)

    目录 一.前言 二.树的概念和定义 三.二叉树 1.基本概念 2.基本形态 3.性质 4.满二叉树 5.完全二叉树 四.存储结构 1.顺序存储 2.二叉链表 3.三叉链表 一.前言 学习目标:理解树和 ...

  6. 数据结构java版txt,图解数据结构:使用Java

    图解数据结构:使用Java 下载 mobi epub pdf ☆☆☆☆☆ 胡昭民 著 下载链接在页面底部 发表于2021-03-10 图书介绍 出版社: 清华大学出版社 ISBN:9787302402 ...

  7. 图解数据结构:栈和队列

    前言 阅读此篇之前,强烈建议先仔细阅读上一篇 图解数据结构:数组和单链表 ,会有事半功倍的效果,并且此篇的代码,基本上是复用上一篇的实现. 上一篇主要讲解了数组和链表这两种线性结构的特点.区别.时间复 ...

  8. 【图解数据结构】栈全面总结

    目录 一.前言 二.基本概念 三.栈的表示和实现 1.顺序栈 2.链栈 四.栈的常见算法实现 1.初始化 2.判空 3.判满 4.顺序栈取栈顶元素 5.顺序栈入栈 6.顺序栈出栈 五.双栈 1.双端顺 ...

  9. 【数据结构入门】队列(Queue)详解(定义、销毁、入队、出队等)| 图解数据结构,超详细哦~

    文章目录 (1)前言 1)队列的概念 2)队列的结构 (2)队列的实现(链式结构) 1)队列的定义 2)队列的初始化 3)队列的销毁 4)入队(尾插) 5)出队(头删) 6)获取队列元素个数 7)获取 ...

最新文章

  1. 关于素数常用结论--威尔逊定理、欧拉定理、费马小定理、米勒罗宾算法
  2. 凯文·凯利:未来很美好,今天仍是Day1
  3. 美团配送A/B评估体系建设与实践
  4. Transformer的七十二变
  5. 设置按峰值带宽计费_如何理解串联谐振电路中的带宽?
  6. 深度学习与TensorFlow:FCN论文学习笔记
  7. 构造activeMQ
  8. 卷积面试题(最重要)
  9. source ubuntu 退出_ubuntu中安装JDK和Tomcat(一)
  10. electron webview 页面加载事件顺序
  11. 修改app的名字和图标
  12. 如何刷新微信服务器小程序版本,微信小程序线上更新版本流程及如何运用
  13. 戴尔服务器怎么win7系统安装系统,戴尔 DELLVostro3400能不能安装windows7系统_戴尔 DELLVostro3400怎么安装win7系统-win7之家...
  14. 盘点飞机上的各种警报
  15. BZOJ 2827 千山鸟飞绝 Treap
  16. 华为软件测试工程师无线产品线实习生第一次视频面试
  17. 谈谈HashMap为什么是线程不安全的?
  18. 全国青少年科技创新大赛全国青少年信息学奥林匹克系列比赛(大赛系列第12期)
  19. 遇到《顺丰速运》app奔溃了,怎么回事呢?
  20. 安卓7.0核心破解示列

热门文章

  1. C#之基本知识和语法3 - 数据类型
  2. python加载项向导_什么是 Python 加载项?
  3. MySQL(三)——函数、事务(ACID)、索引、权限管理和备份、数据库三大范式
  4. Spring Boot AJAX 示例
  5. Keil forc51安装教程
  6. Linux 文件系统详解
  7. 可视化日历(Java实现)
  8. 应届硕士研究生算法岗秋招总结
  9. JDK源码学习-基础
  10. gulp修改服务器端口,2.用gulp建立一个服务器