浅谈算法和数据结构: 七 二叉查找树

前文介绍了符号表的两种实现,无序链表和有序数组,无序链表在插入的时候具有较高的灵活性,而有序数组在查找时具有较高的效率,本文介绍的二叉查找树(Binary Search Tree,BST)这一数据结构综合了以上两种数据结构的优点。

二叉查找树具有很高的灵活性,对其优化可以生成平衡二叉树,红黑树等高效的查找和插入数据结构,后文会一一介绍。

一 定义

二叉查找树(Binary Search Tree),也称有序二叉树(ordered binary tree),排序二叉树(sorted binary tree),是指一棵空树或者具有下列性质的二叉树:

1. 若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;

2. 若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;

3. 任意节点的左、右子树也分别为二叉查找树。

4. 没有键值相等的节点(no duplicate nodes)。

如下图,这个是普通的二叉树:

binary tree

在此基础上,加上节点之间的大小关系,就是二叉查找树:

binary search tree

二 实现

在实现中,我们需要定义一个内部类Node,它包含两个分别指向左右节点的Node,一个用于排序的Key,以及该节点包含的值Value,还有一个记录该节点及所有子节点个数的值Number。

public class BinarySearchTreeSymbolTable<TKey, TValue> : SymbolTables<TKey, TValue> where TKey : IComparable<TKey>, IEquatable<TValue>
{private Node root;private class Node{public Node Left { get; set; }public Node Right { get; set; }public int Number { get; set; }public TKey Key { get; set; }public TValue Value { get; set; }public Node(TKey key, TValue value, int number){this.Key = key;this.Value = value;this.Number = number;}}
...
}

查找

查找操作和二分查找类似,将key和节点的key比较,如果小于,那么就在Left Node节点查找,如果大于,则在Right Node节点查找,如果相等,直接返回Value。

 SearchhitAndSearchMissinBST

该方法实现有迭代和递归两种。

递归的方式实现如下:

public override TValue Get(TKey key)
{TValue result = default(TValue);Node node = root;while (node != null){if (key.CompareTo(node.Key) > 0){node = node.Right;}else if (key.CompareTo(node.Key) < 0){node = node.Left;}else{result = node.Value;break;}}return result;
}

迭代的如下:

public TValue Get(TKey key)
{return GetValue(root, key);
}private TValue GetValue(Node root, TKey key)
{if (root == null) return default(TValue);int cmp = key.CompareTo(root.Key);if (cmp > 0) return GetValue(root.Right, key);else if (cmp < 0) return GetValue(root.Left, key);else return root.Value;
}

插入

插入和查找类似,首先查找有没有和key相同的,如果有,更新;如果没有找到,那么创建新的节点。并更新每个节点的Number值,代码实现如下:

public override void Put(TKey key, TValue value)
{root = Put(root, key, value);
}private Node Put(Node x, TKey key, TValue value)
{//如果节点为空,则创建新的节点,并返回//否则比较根据大小判断是左节点还是右节点,然后继续查找左子树还是右子树//同时更新节点的Number的值if (x == null) return new Node(key, value, 1);int cmp = key.CompareTo(x.Key);if (cmp < 0) x.Left = Put(x.Left, key, value);else if (cmp > 0) x.Right = Put(x.Right, key, value);else x.Value = value;x.Number = Size(x.Left) + Size(x.Right) + 1;return x;
}private int Size(Node node)
{if (node == null) return 0;else return node.Number;
}

  插入操作图示如下:

insert into BST

下面是插入动画效果:

insert into BST

随机插入形成树的动画如下,可以看到,插入的时候树还是能够保持近似平衡状态:

Insert keys random order in BST

最大最小值

如下图可以看出,二叉查找树的最大最小值是有规律的:

the max and min item in bst

从图中可以看出,二叉查找树中,最左和最右节点即为最小值和最大值,所以我们只需迭代调用即可。

public override TKey GetMax()
{TKey maxItem = default(TKey);Node s = root;while (s.Right != null){s = s.Right;}maxItem = s.Key;return maxItem;
}public override TKey GetMin()
{TKey minItem = default(TKey);Node s = root;while (s.Left != null){s = s.Left;}minItem = s.Key;return minItem;
}

以下是递归的版本:

public TKey GetMaxRecursive()
{return GetMaxRecursive(root);
}private TKey GetMaxRecursive(Node root)
{if (root.Right == null) return root.Key;return GetMaxRecursive(root.Right);
}public TKey GetMinRecursive()
{return GetMinRecursive(root);
}private TKey GetMinRecursive(Node root)
{if (root.Left == null) return root.Key;return GetMinRecursive(root.Left);
}

Floor和Ceiling

查找Floor(key)的值就是所有<=key的最大值,相反查找Ceiling的值就是所有>=key的最小值,下图是Floor函数的查找示意图:

floor  function in BST

以查找Floor为例,我们首先将key和root元素比较,如果key比root的key小,则floor值一定在左子树上;如果比root的key大,则有可能在右子树上,当且仅当其右子树有一个节点的key值要小于等于该key;如果和root的key相等,则floor值就是key。根据以上分析,Floor方法的代码如下,Ceiling方法的代码类似,只需要把符号换一下即可:

public TKey Floor(TKey key)
{Node x = Floor(root, key);if (x != null) return x.Key;else return default(TKey);
}private Node Floor(Node x, TKey key)
{if (x == null) return null;int cmp = key.CompareTo(x.Key);if (cmp == 0) return x;if (cmp < 0) return Floor(x.Left, key);else{Node right = Floor(x.Right, key);if (right == null) return x;else return right;}
}

删除

删除元素操作在二叉树的操作中应该是比较复杂的。首先来看下比较简单的删除最大最小值得方法。

以删除最小值为例,我们首先找到最小值,及最左边左子树为空的节点,然后返回其右子树作为新的左子树。操作示意图如下:

delete minimun in BST

代码实现如下:

public void DelMin()
{root = DelMin(root);
}private Node DelMin(Node root)
{if (root.Left == null) return root.Right;root.Left = DelMin(root.Left);root.Number = Size(root.Left) + Size(root.Right) + 1;return root;
}

删除最大值也是类似。

现在来分析一般情况,假定我们要删除指定key的某一个节点。这个问题的难点在于:删除最大最小值的操作,删除的节点只有1个子节点或者没有子节点,这样比较简单。但是如果删除任意节点,就有可能出现删除的节点有0个,1 个,2个子节点的情况,现在来逐一分析。

当删除的节点没有子节点时,直接将该父节点指向该节点的link设置为null。

 delete node which has 0 childrens

当删除的节点只有1个子节点时,将该自己点替换为要删除的节点即可。

delete node which has 1 childrens

当删除的节点有2个子节点时,问题就变复杂了。

假设我们删除的节点t具有两个子节点。因为t具有右子节点,所以我们需要找到其右子节点中的最小节点,替换t节点的位置。这里有四个步骤:

1. 保存带删除的节点到临时变量t

2. 将t的右节点的最小节点min(t.right)保存到临时节点x

3. 将x的右节点设置为deleteMin(t.right),该右节点是删除后,所有比x.key最大的节点。

4. 将x的做节点设置为t的左节点。

整个过程如下图:

delete node which has 2 childrens

对应代码如下:

public void Delete(TKey key)
{root =Delete(root, key);}private Node Delete(Node x, TKey key)
{int cmp = key.CompareTo(x.Key);if (cmp > 0) x.Right = Delete(x.Right, key);else if (cmp < 0) x.Left = Delete(x.Left, key);else{if (x.Left == null) return x.Right;else if (x.Right == null) return x.Left;else{Node t = x;x = GetMinNode(t.Right);x.Right = DelMin(t.Right);x.Left = t.Left;}}x.Number = Size(x.Left) + Size(x.Right) + 1;return x;
}private Node GetMinNode(Node x)
{if (x.Left == null) return x;else return GetMinNode(x.Left); 
}

以上二叉查找树的删除节点的算法不是完美的,因为随着删除的进行,二叉树会变得不太平衡,下面是动画演示。

delete node in BST

三 分析

二叉查找树的运行时间和树的形状有关,树的形状又和插入元素的顺序有关。在最好的情况下,节点完全平衡,从根节点到最底层叶子节点只有lgN个节点。在最差的情况下,根节点到最底层叶子节点会有N各节点。在一般情况下,树的形状和最好的情况接近。

BST Tree shape

在分析二叉查找树的时候,我们通常会假设插入的元素顺序是随机的。对BST的分析类似与快速排序中的查找:

BST and quick sort partition

BST中位于顶部的元素就是快速排序中的第一个划分的元素,该元素左边的元素全部小于该元素,右边的元素均大于该元素。

对于N个不同元素,随机插入的二叉查找树来说,其平均查找/插入的时间复杂度大约为2lnN,这个和快速排序的分析一样,具体的证明方法不再赘述,参照快速排序。

 

四 总结

有了前篇文章 二分查找的分析,对二叉查找树的理解应该比较容易。下面是二叉查找树的时间复杂度:

analysis of binary search tree

它和二分查找一样,插入和查找的时间复杂度均为lgN,但是在最坏的情况下仍然会有N的时间复杂度。原因在于插入和删除元素的时候,树没有保持平衡。我们追求的是在最坏的情况下仍然有较好的时间复杂度,这就是后面要讲的平衡查找树的内容了。下文首先讲解平衡查找树的最简单的一种:2-3查找树。

希望本文对您了解二叉查找树有所帮助。

转载于:https://www.cnblogs.com/yangecnu/p/Introduce-Binary-Search-Tree.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/377188.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

scala部分应用函数_Scala中的部分函数

scala部分应用函数Scala部分功能 (Scala partial functions) A partial function is a function that returns values only for a specific set of values i.e. this function is not able to return values for some input values. This function is defined so that only som…

《MySQL——备库多线程复制策略。》

目录备库并行复制能力MySQL5.6版本 并行复制策略MariaDB 并行复制策略MySQL5.7版本 并行复制策略MySQL5.7.22版本 并行复制策略总结备库并行复制能力 主要涉及两个方面的并行度&#xff1a; 1、客户端写入主库的能力 2、备库上sql_thread执行中转日志relay log 1的并行能力…

人脸是门大生意

我们正处在一个新时代的入口。人有70%的能量是被大脑消耗&#xff0c;大脑90%的能量用来处理视觉信息&#xff0c;人脸则承载了绝大部分的视觉信息。我们要讨论的是一个比Google Glass更酷的世界。文/程苓峰-云科技网易邮箱的用户已经可以用人脸而不是密码来验证登陆。安卓4.0实…

【SQL】sql版Split函数。用于拆分字符串为单列表格

【SQL】sql版Split函数。用于拆分字符串为单列表格 功能与.net版string.Split函数类似&#xff0c;只不过.net返回的是数组&#xff0c;这个返回的是一个单列表格&#xff0c;每个拆分出来的子串占一行。可选是否移除空格子串和重复项。市面上类似的函数不算少&#xff0c;但大…

线描算法

线描算法 (Line drawing algorithms) The equation for a straight line is ymxb 直线方程为y mx b In this m represent a slope of a line which can be calculated by the my2-y1/x2-x1 where (x1, y1) are the starting position of the points and (x2, y2) are the end…

为移动端网页构造快速响应按钮

背景 在谷歌&#xff0c;我们不断地推测手机网页应用的可能性。像HTML5这样的技术使我们网页版的应用以及运行在手机设备上的原生应用。而这些技术的成就之一就是我们开发了一种新的创建按钮的方法&#xff0c;使按钮的响应时间远远快于一般的HTML按钮。在此之前的按钮或者其他…

Red Gate系列之一 SQL Compare 10.4.8.87 Edition 数据库比较工具 完全破解+使用教程

Red Gate系列之一 SQL Compare 10.4.8.87 Edition 数据库比较工具 完全破解使用教程 Red Gate系列文章&#xff1a; Red Gate系列之一 SQL Compare 10.4.8.87 Edition 数据库比较工具 完全破解使用教程 Red Gate系列之二 SQL Source Control 3.0.13.4214 Edition 数据库版本控制…

《MySQL——基于位点orGTID的主备切换协议》

一主多从的设置&#xff0c;用于读写分离&#xff0c;主库负责所有的写入和一部分读&#xff0c;其他读请求则由从库分担。 一主多从架构下&#xff0c;主库故障后的主备切换问题。相比于一主一备&#xff0c;多了从库指向新主库的过程。 基于位点的主备切换同步 把节点B设…

数据科学和统计学_数据科学中的统计

数据科学和统计学统计 (Statistics) Statistics are utilized to process complex issues in reality with the goal that Data Scientists and Analysts can search for important patterns and changes in Data. In straightforward words, Statistics can be utilized to ge…

java随机数生成(固定位数)

随机生成 a 到 b (不包含b)的整数:(int)(Math.random()*(b-a))a; 随机生成 a 到 b (包含b)的整数:(int)(Math.random()*(b-a1))a;转载于:https://www.cnblogs.com/zhwl/p/3624726.html

POJ 3670 Eating Together

POJ_3670 由于递增和递减是类似的&#xff0c;下面不妨只讨论变成递增序列的情况。 由于Di只有三个数&#xff0c;所以可以考虑将序列分割成三部分&#xff0c;第一部分全部变成1&#xff0c;第二部分全部变成2&#xff0c;第三部分全部变成3。然后我们枚举3开始的位置&#xf…

《MySQL——如何解决一主多从的读写分离的过期读问题》

目录两种架构两种架构特点强制走主库方案Sleep方案判断主备无延迟方案配合semi-sync等主库位点方案GTID方案两种架构 基于一主多从的读写分离&#xff0c;如何处理主备延迟导致的读写分离问题。 读写分离的主要目标&#xff1a;分摊主库压力。 有两种架构&#xff1a; 1、客…

json/ 发送形式_24/7的完整形式是什么?

json/ 发送形式24/7&#xff1a;二十四 (24/7: Twenty-Four Seven) 24/7 or 24-7 service, which generally marked "twenty-four seven" is service that is existing at any time and typically, every day in trade business and industry. Substitute orthograph…

《MySQL tips:并发查询与并发连接区别》

并发连接与并发查询&#xff0c;并不是一个概念。 在执行show processlist的结果里&#xff0c;看到了几千个连接&#xff0c;指的是并发连接。 而"当前正在执行"的语句&#xff0c;才是并发查询。 并发连接数多影响的是内存。 并发查询太高对CPU不利。一个机器的…

对上拉下拉电阻的作用作个总结(想了解的过来看看)(转载)

转自&#xff1a;http://www.amobbs.com/thread-5475279-1-3.html 一、定义&#xff1a;上拉就是将不确定的信号通过一个电阻嵌位在高电平&#xff01;电阻同时起限流作用&#xff01;下拉同理&#xff01;上拉是对器件注入电流&#xff0c;下拉是输出电流&#xff1b;弱强只是…

给用户传入的变量进行转义操作

先看代码实现&#xff1a; /* 对用户传入的变量进行转义操作。*/ if (!get_magic_quotes_gpc()) {if (!empty($_GET)){$_GET addslashes_deep($_GET);}if (!empty($_POST)){$_POST addslashes_deep($_POST);}$_COOKIE addslashes_deep($_COOKIE);$_REQUEST addslashes_…

《MySQL——外部检测与内部统计 判断 主库是否出现问题》

目录select1判断查表判断更新判断外部检测弊端内部统计一主一备的双M架构里&#xff0c;主备切换只需要把客户端流量切换到备库。 在一主多从的架构里&#xff0c;主备切换要把客户端流量切换到备库&#xff0c;也需要把从库接到新主库上。 切换有两种场景&#xff1a;1、主动…

NIM的完整形式是什么?

NIM&#xff1a;无内部消息 (NIM: No Internal Message) NIM is an abbreviation of "No Internal Message". NIM是“无内部消息”的缩写。 It is an expression, which is commonly used in the Gmail platform. It is written in the subject of the mail, if the…

[Json] C#ConvertJson|List转成Json|对象|集合|DataSet|DataTable|DataReader转成Json (转载)...

点击下载 ConvertJson.rar 本类实现了 C#ConvertJson|List转成Json|对象|集合|DataSet|DataTable|DataReader转成Json|等功能大家先预览一下 请看代码 /// <summary> /// 类说明&#xff1a;Assistant /// 编 码 人&#xff1a;苏飞 /// 联系方式&#xff1a;361983679 …

let 只能在严格模式下吗_LET的完整形式是什么?

let 只能在严格模式下吗LET&#xff1a;今天早早离开 (LET: Leaving Early Today) LET is an abbreviation of "Leaving Early Today". LET是“ Leaveing Today Today”的缩写 。 It is an expression, which is commonly used in the Gmail platform. It is writt…