我想知道二叉树的具体应用是什么。你能举几个实际的例子吗?
争论二叉树的性能是毫无意义的 - 它们不是一种数据结构,而是一类数据结构,具有不同的性能特点。虽然不平衡的二叉树在搜索方面表现比自平衡的二叉树差得多,但有许多二叉树(例如二进制试图),对于它们来说,"平衡"没有意义。
map
和set
对象。当大多数人谈论二叉树时,他们往往想到的是二叉 搜索 树,因此我将首先介绍它。
非平衡的二叉搜索树实际上只有在为学生讲授数据结构时才有用。这是因为,除非数据以相对随机的顺序进入,否则树很容易变成其最坏情况,即链表,因为简单的二叉树不是平衡的。
举个例子:我曾经需要修复一些软件,它把数据加载到二叉树中进行操作和搜索。它按排序形式将数据写出:
Alice
Bob
Chloe
David
Edwina
Frank
因此,当读回它时,最终得到以下树形结构:
Alice
/ \
= Bob
/ \
= Chloe
/ \
= David
/ \
= Edwina
/ \
= Frank
/ \
= =
这是退化形式。如果你在那棵树上寻找 Frank,你需要搜索六个节点才能找到他。
当二叉树平衡时,它们变得非常有用,可以用于搜索。这涉及通过它们的根节点旋转子树,使得任何两个子树之间的高度差小于或等于1。将上述名称逐个添加到平衡树中将给出以下序列:
1. Alice
/ \
= =
2. Alice
/ \
= Bob
/ \
= =
3. Bob
_/ \_
Alice Chloe
/ \ / \
= = = =
4. Bob
_/ \_
Alice Chloe
/ \ / \
= = = David
/ \
= =
5. Bob
____/ \____
Alice David
/ \ / \
= = Chloe Edwina
/ \ / \
= = = =
6. Chloe
___/ \___
Bob Edwina
/ \ / \
Alice = David Frank
/ \ / \ / \
= = = = = =
实际上,当添加条目时(在步骤3和6中),您可以看到整个子树向左旋转,这将为您提供一个平衡的二叉树,在该树中最坏情况下的查找时间复杂度为O(log N)
,而不是退化形式所给出的O(N)
。在任何时候,最高的NULL(=
)与最低的NULL之间的层数差异都不超过一个级别。在上面的最终树中,您只需查看三个节点(Chloe
,Edwina
和最后一个Frank
),即可找到Frank。
当然,当您将它们变成平衡的多路树而不是二叉树时,它们可以变得更加有用。这意味着每个节点保存多个项目(技术上,它们保存N个项目和N+1个指针,二叉树是一种特殊情况的1路多路树,具有1个项目和2个指针)。
通过使用三路树,您最终会得到:
Alice Bob Chloe
/ | | \
= = = David Edwina Frank
/ | | \
= = = =
这通常用于维护项目索引的键。我编写了针对硬件进行优化的数据库软件,其中节点恰好是磁盘块大小(例如,512字节),并且将尽可能多的键放入单个节点中。在这种情况下,指针实际上是记录号,而该记录号是与索引文件分开的固定长度记录直接存取文件中的位置(因此可以通过只查找到X * record_length来找到记录号X)。二叉树是一种树形数据结构,每个节点最多只有两个子节点,通常称为“左”和“右”。有子节点的节点是父节点,子节点可能包含对其父节点的引用。在树之外,如果存在,通常会有一个对“根”节点(所有节点的祖先)的引用。可以通过从根节点开始重复跟随左或右子节点的引用来访问数据结构中的任何节点。在二叉树中,每个节点的度数最大为两。
二叉树非常有用,因为如图片所示,如果要查找树中的任何节点,您只需要最多查找6次。例如,如果要搜索节点24,则应从根节点开始:
下图说明了此搜索过程:
您可以看到,在第一次搜索中,可以排除整个树的一半节点,在第二次搜索中,可以排除左子树的一半节点。这使得搜索非常有效率。如果应用于4 亿元素,则最多只需搜索32次。因此,包含在树中的元素越多,您的搜索就越有效率。
删除可能变得很复杂。如果节点有0或1个子节点,则只需移动一些指针以排除要删除的节点即可。但是,您不能轻易地删除具有2个子节点的节点。因此我们采取了一种捷径。假设我们想要删除节点19。
由于尝试确定要将左右指针移动到何处并不容易,因此我们找到一个替换它的方法。我们进入左子树,并尽可能向右移动。这为我们提供了要删除的节点的下一个最大值。
现在我们复制所有18的内容,除了左右指针以外,并删除原始的18节点。
为了创建这些图像,我实现了AVL树,一种自平衡树,因此在任何时刻,树的叶节点(没有子节点的节点)之间最多只有一级差异。这使树不会变得倾斜,并保持最大的O(log n)
搜索时间,代价是需要更多的时间进行插入和删除。
以下是一个示例,显示我的AVL树如何尽可能保持紧凑和平衡。
在已排序的数组中,查找仍然需要O(log(n))
的时间,就像树一样,但随机插入和删除则需要 O(n)
的时间,而不是树的 O(log(n))
。一些STL容器利用这些性能特点来使插入和删除时间最多为O(log n)
,非常快速。其中一些容器是map
、multimap
、set
和multiset
。T& left, right
,其中T
表示节点类型。 - undefined一个有趣的未被提及的二叉树示例是递归计算数学表达式。从实用角度来看,它基本上是无用的,但它是一种有趣的思考这类表达式的方式。
基本上,树的每个节点都有一个值,该值要么固有自身,要么通过对其子节点的值进行递归计算得出。
例如,表达式(1+3)*2
可以表示为:
*
/ \
+ 2
/ \
1 3
为了求出这个表达式的值,我们要求父节点的值。这个父节点又从它的孩子节点中获得值,其中一个是加号操作符,另一个节点仅包含数字'2'。加号操作符又从它的两个子节点(值为'1'和'3')获取值并将它们相加,返回4给乘法节点,最终返回8。主要应用是二叉搜索树。它是一种数据结构,其中搜索、插入和删除都非常快(大约需要log(n)
次操作)。
其中一个最常见的应用是以排序形式高效地存储数据,以便快速访问和搜索存储的元素。例如,在C++标准库中的std::map
或std::set
。
作为数据结构的二叉树对于表达式解析器和求解器的各种实现非常有用。
它也可以用于解决一些数据库问题,例如索引。
通常,二叉树是特定基于树的数据结构的概括性概念,不同属性的各种具体类型的二叉树可以被构建出来。