数据结构和算法学习笔记十三:二叉排序树
一.简介
1.概念:二叉排序树(Binary Sort Tree)又称为二叉查找树.它或者是一颗空树,在不是空树时满足以下条件:
1)左子树不为空,则左子树上所有结点的值均小于它的根结构的值;
2)右子树不为空,则右子树上所有结点的值均大于它的根结构的值;
3)左右子树也分别是二叉排序树.
2.二叉排序树的遍历有前序\中序\后序遍历等方式,遍历时先左右结点再根结点称为后序遍历,先根结点再左右结点称为前序遍历,先左结点再根结点最后右节点称为中序遍历(根据遍历时根结点的位置命名).进行中序遍历后可以得到一个有序的序列.
二.二叉排序树的增删改查
向二叉排序树种添加结点\查询结点的方式都比较简单,将要增加或查询的值每次都和父节点的值比较,确定这个值应该放在父节点的左子结点或是右子结点上,如果该子结点不为空,则继续判断向下一层放置.但是删除和修改值得操作却不好实现.
二叉排序树删除时有以下几种可能性:
1)要删除的结点为叶子结点,直接删除即可;
2)要删除的结点只有左子结点或右子结点,将左子结点或有子结点的父节点修改为当前结点的父节点即可;
3)要删除的结点同时有左子结点又有右子结点时,如果重新遍历左子树或者右子树的每一个结点并重新添加,计算量比较大.我们一般采用这种方案进行:将左子树中的最右叶子结点(左子树中的最大值)或者右子树中的最左叶子结点(右子树中的最小值)的位置和当前要删除结点的位置交换,要删除的结点就成为了叶子结点,就可以删去了.这种方案能够完成删除的原因是如果将所有结点构造成一个有序序列,那么左子树中的最右子结点和右子树中的最左子结点就是序列中当前结点的左右结点(离当前结点最近的两个结点),所以将这两个结点中的任意一个和当前结点替换再删掉当前结点后这棵树仍然是一颗二叉排序树.
二叉排序树的值修改可以分解为一次删除操作和一次添加操作.
三.二叉排序树的增删查的代码实现(C#)
/************************************ * 创建人:movin * 创建时间:2021/7/22 20:28:50 * 版权所有:个人 ***********************************/ using System; using System.Collections.Generic; using System.Text; namespace SearchCore { /// <summary> /// 结点数据结构 /// </summary> public class Node { /// <summary> /// 结点中的键值 /// </summary> public int Key { get; set; } /// <summary> /// 结点的左子结点 /// </summary> public Node LeftChild { get; set; } /// <summary> /// 结点的右子结点 /// </summary> public Node RightChild { get; set; } public Node(int key) { Key = key; } } }
/************************************ * 创建人:movin * 创建时间:2021/7/22 20:29:49 * 版权所有:个人 ***********************************/ using System; using System.Collections.Generic; using System.Text; namespace SearchCore { public class BinarySortTree { /// <summary> /// 根结点 /// </summary> public Node root { get; private set; } /// <summary> /// 查找结点是否存在 /// </summary> /// <param name="key"></param> /// <returns>查找到的结点,没有查找到时返回null</returns> public Node SearchNode(int key) { return RealSearchNode(root, key); } /// <summary> /// 真正查找结点的方法 /// </summary> /// <param name="node"></param> /// <returns></returns> private Node RealSearchNode(Node node, int key) { //递归终止条件一:当前结点为空 if(node == null) { return null; } if(key < node.Key) { return RealSearchNode(node.LeftChild, key); } if(key > node.Key) { return RealSearchNode(node.RightChild, key); } //递归终止条件二:当前结点就是要找的结点 return node; } /// <summary> /// 插入结点 /// </summary> /// <param name="key"></param> /// <returns>插入的结点,已有该结点时返回null</returns> public Node InsertNode(int key) { if(root == null) { root = new Node(key); return root; } return RealInsertNode(root, key); } /// <summary> /// 真正插入结点的方法 /// </summary> /// <param name="node"></param> /// <param name="key"></param> /// <returns></returns> private Node RealInsertNode(Node node,int key) { //要插入的值比当前值小,插入到左子树上 if(key < node.Key) { if(node.LeftChild == null) { node.LeftChild = new Node(key); return node.LeftChild; } else { return RealInsertNode(node.LeftChild, key); } } //要插入的值比当前值大,插入到右子树上 if(key > node.Key) { if(node.RightChild == null) { node.RightChild = new Node(key); return node.RightChild; } else { return RealInsertNode(node.RightChild, key); } } //要插入的值和当前值相等,插入失败,返回null return null; } /// <summary> /// 移除结点 /// </summary> /// <param name="key"></param> /// <returns>被移除的结点,没有成功移除(不存在该结点)时返回null</returns> public Node RemoveNode(int key) { if(root == null) { return null; } return RealRemoveNode(root, null, key); } /// <summary> /// 真正移除结点的方法 /// </summary> /// <param name="node"></param> /// <param name="key"></param> /// <returns></returns> private Node RealRemoveNode(Node node,Node parentNode,int key) { //递归终止条件一:结点为空 if(node == null) { return null; } //找到要移除的结点 if(key < node.Key) { return RealRemoveNode(node.LeftChild, node, key); } if(key > node.Key) { return RealRemoveNode(node.RightChild, node, key); } //递归终止条件二:移除结点 if(node.LeftChild == null) //左结点为空时不论右节点是否为空,直接把右节点接到父节点上 { if(key < parentNode.Key) { parentNode.LeftChild = node.RightChild; } else { parentNode.RightChild = node.RightChild; } node.RightChild = null; return node; } if(node.RightChild == null) //右节点为空时,直接把左结点接到父节点上 { if (key < parentNode.Key) { parentNode.LeftChild = node.LeftChild; } else { parentNode.RightChild = node.LeftChild; } node.LeftChild = null; return node; } //左右结点都不为空时,找左子树的最右子结点(二叉排序树构成的有序序列中当前结点的左相邻结点) Node leftNeighbor = node.LeftChild; Node leftNeighborParent = node; while(leftNeighbor.RightChild != null) { leftNeighborParent = leftNeighbor; leftNeighbor = leftNeighbor.RightChild; } //交换结点,这里直接交换数据,如果结点中存储了其他数据(如索引),将这些数据一并交换 node.Key = node.Key ^ leftNeighbor.Key; leftNeighbor.Key = node.Key ^ leftNeighbor.Key; node.Key = node.Key ^ leftNeighbor.Key; //交换完成后再移除结点,这时结点的右子树一定为空 //但是需要判断,如果上面的while循环没有进,应该将结点的左子树挂到父节点的左子树上 if(leftNeighborParent == node) { leftNeighborParent.LeftChild = leftNeighbor.LeftChild; } else { leftNeighborParent.RightChild = leftNeighbor.LeftChild; } leftNeighbor.LeftChild = null; return leftNeighbor; } } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!