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

前文介绍了符号表的两种实现,无序链表和有序数组,无序链表在插入的时候具有较高的灵活性,而有序数组在查找时具有较高的效率,本文介绍的二叉查找树(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,一经查实,立即删除!

相关文章

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

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

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

背景 在谷歌&#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…

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

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

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

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

[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 …

《MySQL——恢复数据-误删行、表、库》

目录误删行事前预防误删行数据方法误删表/库延迟复制备库事前预防误删库/表方法传统的架构不能预防误删数据&#xff0c;因为主库的一个drop table命令&#xff0c;会通过binlog传给所有从库和级联从库&#xff0c;进而导致整个集群的实例都会执行这个命令。 MySQL相关的误删除…

python图例位置_Python | 图例位置

python图例位置Legends are one of the key components of data visualization and plotting. Matplotlib can automatically define a position for a legend in addition to this, it allows us to locate it in our required positions. Following is the list of locations…

工作总结:文件对话框的分类(C++)

原文地址&#xff1a;http://www.jizhuomi.com/software/173.html 文件对话框分为打开文件对话框和保存文件对话框&#xff0c;相信大家在Windows系统中经常见到这两种文件对话框。例如&#xff0c;很多编辑软件像记事本等都有“打开”选项&#xff0c;选择“打开”后会弹出一个…

《MySQL——Innodb改进LRU算法》

Innodb改进LRU.算法&#xff0c;实质上将内存链表分成两段。 靠近头部的young和靠近末尾的old&#xff0c;取5/12段为分界。 新数据在一定时间内只能在old段的头部&#xff0c;当在old段保持了一定的时间后被再次访问才能升级到young。 实质上是分了两段lru&#xff0c;这样做的…

jQuery: 整理4---创建元素和添加元素

1.创建元素&#xff1a;$("内容") const p "<p>这是一个p标签</p>" console.log(p)console.log($(p)) 2. 添加元素 2.1 前追加子元素 1. 指定元素.prepend(内容) -> 在指定元素的内部的最前面追加内容&#xff0c;内容可以是字符串、…

Design a high performance cache for multi-threaded environment

如何设计一个支持高并发的高性能缓存库 不 考虑并发情况下的缓存的设计大家应该都比较清楚&#xff0c;基本上就是用map/hashmap存储键值&#xff0c;然后用双向链表记录一个LRU来用于缓存的清理。这篇文章 应该是讲得很清楚http://timday.bitbucket.org/lru.html。但是考虑到高…

LinkChecker 8.1 发布,网页链接检查

LinkChecker 8.1 可对检查时间和最大的 URL 数量进行配置&#xff1b;当使用 HTTP 请求时发送 do-not-track 头&#xff1b;生成 XML 的 sitemap 用于搜索引擎优化&#xff1b;检测 URL 长度和重复的页面内容&#xff1b;修复了很多检查的 bug。 LinkChecker 是一个网页链接检查…

c语言语言教程0基础_C语言基础

c语言语言教程0基础Hey, Folks here I am back with my second article on C language. Hope you are through with my previous article C language - History, Popularity reasons, Characteristics, Basic structure etc. In this one, I will cover some fundamental conce…

《MySQL——临时表》

内存表与临时表区别 临时表&#xff0c;一般是人手动创建。 内存表&#xff0c;是mysql自动创建和销毁的。 内存表&#xff0c;指的是使用Memory引擎的表&#xff0c;建表语法&#xff1a;create table ... engine memeory 表的数据存在内存里&#xff0c;系统重启后会被清…

drei

模拟9 T3 &#xff08;COGS上也有&#xff0c;链接http://218.28.19.228/cogs/problem/problem.php?pid1428&#xff09; 题目描述 输入a&#xff0c;p&#xff0c;求最小正整数x&#xff0c;使得a^x mod p 1。 分析 神奇的欧拉定理&#xff08;对于gcd&#xff08;a&#xf…

css中变量_CSS中的变量

css中变量CSS | 变数 (CSS | Variables) CSS variables allow you to create reusable values that can be used throughout a CSS document. CSS变量允许您创建可在CSS文档中使用的可重用值。 In CSS variable, function var() allows CSS variables to be accessed. 在CSS变…

SuperSpider——打造功能强大的爬虫利器

SuperSpider——打造功能强大的爬虫利器 博文作者&#xff1a;加菲 发布日期&#xff1a;2013-12-11 阅读次数&#xff1a;4506 博文内容&#xff1a; 1.爬虫的介绍 图1-1 爬虫&#xff08;spider) 网络爬虫(web spider)是一个自动的通过网络抓取互联网上的网页的程序&#xf…