二叉搜索树的定义
和前面一篇讨论二叉树有一点不一样,二叉搜索树它本身是一种二叉树,但是它有一个特殊的地方。任何一个二叉树中间的节点都是可以比较的。他们有一个key的值用于比较节点之间的大小。而且,对于任意一个二叉搜索树中间的节点,它左子树中间的节点值小于它,而它右子树的节点值则大于或等于它。一个典型的搜索二叉树如下图所示:
在有的情况下,我们为了实现某些方法方便会在树中间增加一些属性,比如指向父节点的引用,当前节点所有子节点元素个数。
下面是一种二叉树节点的定义:
private static class BinaryNodeWithSize<T> extends BinaryNode<T> { BinaryNodeWithSize(T x) { super(x); size = 0; } int size; } class BinaryNode<T> { T element; BinaryNode<T> left; BinaryNode<T> right; BinaryNode<T> parent; BinaryNode(T element) { this.element = element; left = right = parent = null; } }
这里通过继承的方式实现一种包含指向父节点引用和子节点个数的节点类型。
前驱(predecessor)
前驱指的是对指定的一个节点找到一个比它小但是和它值最接近的节点。要求一个节点的前驱比较理想的情况是需要用到节点指向父节点的引用。在具体查找元素的时候需要向上访问。我们主要针对两种情况进行讨论:
1. 如果该节点有左子树,那么和它最接近的那个前驱元素肯定是左子树的最大值。这种情况对应的图如下所示:
实际上这种情况就是要找到节点左子树的最大值。
2. 如果该节点没有左子树,我们需要向上来查找。对于7这个节点来说,我们需要找到比它小的。但是如果它是它父节点的左儿子,则该父节点比它大,不符合要求。需要一直找到将它或者它上面的节点作为右儿子的那个节点。如下图:
所以针对这种情况我们可以得出如下的代码:
public BinaryNode<T> predecessor(BinaryNode<T> t) { if(t != null) { if(t.left != null) return findMax(t.left); BinaryNode<T> y = t.parent; while(y != null && t == y.left) { t = y; y = y.parent; } return y; } return null; }
里面的findMax实现如下:
private BinaryNode<T> findMax(BinaryNode<T> t) { if(t != null) while(t.right != null) t = t.right; return t; }
后继(successor)
后继节点就是比当前节点大的节点集合中最小的那个。求节点的后继和前驱类似。也要考虑两种情况。
1. 如果它有右子树,肯定后继是右子树的最小值。
2. 如果它没有右子树,则后继节点是它向上的树中第一个以它的上级节点为左子树的那个节点。如下图所示:
对应的实现代码则如下:
public BinaryNode<T> successor(BinaryNode<T> t) { if(t != null) { if(t.right != null) return findMin(t.right); BinaryNode<T> y = t.parent; while(y != null && t == y.right) { t = y; y = y.parent; } return y; } return null; } protected BinaryNode<T> findMin(BinaryNode<T> t) { if(t != null) while(t.left != null) t = t.left; return t; }
插入元素(insert)
假定我们要插入一个元素,最基本的思路就是先根据这个要插入的值和树中间的节点进行比较,如果该节点比目标值大,则在节点的左子树里面寻找插入的地方,否则在右子树里面寻找。这里的一个实现用到了递归的思路,使得很多需要考虑的细节都被简化了:
protected BinaryNode<T> insert(T x, BinaryNode<T> tt) { BinaryNodeWithSize<T> t = (BinaryNodeWithSize<T>) tt; if(t == null) t = new BinaryNodeWithSize<T>(); else if(x.compareTo(t.element) < 0) t.left = insert(x, t.left); else if(x.compareTo(t.element) > 0) t.right = insert(x, t.right); else throw new DuplicateItemException(x.toString()); t.size++; return t; }
这里递归的一个妙用就是在每次要插入一个元素的时候,我们需要修改经历过的节点size值。size表示该节点下面所有子树元素的个数。通过返回修改后的根节点很多需要考虑修改的细节都通过递归给自动实现了。
如果我们考虑一个非递归的版本实现,则会发现要繁琐一些,当然,这里返回的不再是修改后的树的根节点,而是当前插入的元素节点:
protected BinaryNode<T> insertIter(T x, BinaryNode<T> t) { BinaryNode<T> prev = null, node = t; while(t != null) { prev = t; if(x.compareTo(t.element) < 0) { t.size++; t = t.left; } else if(x.compareTo(t.element) > 0) { t.size++; t = t.right; } else throw new DuplicateItemException(x.toString()); } if(prev == null) node = new BinaryNode<T>(x); else { if(x.compareTo(prev.element) < 0) { prev.left = new BinaryNode<T>(x); prev.left.parent = prev; } else { prev.right = new BinaryNode<T>(x); prev.right.parent = prev; } } return node; }
通过比较这两部分的代码,我们会发现使用递归有的时候确实可以隐藏了很多需要考虑的细节,而且代码也会简单很多。
删除元素(remove)
和前面添加元素的方式相反,这里要找到需要删除的元素然后删除它。删除元素的过程可能会复杂一点。针对它所在节点的情况。我们分别进行讨论。
1. 如果该节点是一个叶节点。
直接删除该元素,将该父节点所指向它的引用置为null。
2. 如果该节点只有一个子树
将它的左/右子节点替换它本身。
3. 如果该节点有两个子树
笼统的来说,既然它有左右两个子节点,可以取它的后继元素,也就是右子树的最小值来替换它。然后在右子树中删除这个最小值节点。根据右子节点是否有左子树,具体的形式还会稍微有点不一样。
一种情况如下,右子节点没有左子树,这意味着它的右子节点就是右子树最小的元素:
如果节点的右子节点有左子树的话,则对应下面的情形:
针对前面的那些讨论,下面是remove方法的实现:
protected BinaryNode<T> remove(T x, BinaryNode<T> tt) { BinaryNodeWithSize<T> t = (BinaryNodeWithSize<T>) tt; if(t == null) throw new ItemNotFoundException(x.toString()); if(x.compareTo(t.element) < 0) t.left = remove(x, t.left); else if(x.compareTo(t.element) > 0) t.right = remove(x, t.right); else if(t.left != null && t.right != null) { t.element = findMin(t.right).element; t.right = removeMin(t.right); } else return (t.left != null) ? t.left : t.right; t.size--; return t; } protected BinaryNode<T> removeMin(BinaryNode<T> tt) { BinaryNodeWithSize<T> t = (BinaryNodeWithSize<T>) tt; if(t == null) throw new ItemNotFoundException(); if(t.left == null) return t.right; t.left = removeMin(t.left); t.size--; return t; }
remove方法在这里采用递归的方式返回待删除子树的根节点,和前面的思路类似。
第k小的元素
在节点增加了size属性之后,我们需要求这个第k小元素的问题就比较简单了。首先我们从树的根节点开始,比较这个目标k值和它的左子节点的元素个数,如果它小于或者等于左子节点元素个数的话,则需要在左子树里面继续查找。如果k大于左子树个数+1的话,则在右子树里查找k-t.left.size - 1。这里判断查找到的条件是k == t.left.size + 1。因为这正好表示k的值和当前节点对应。
protected BinaryNode<T> findKth(int k, BinaryNode<T> t) { if(t == null) throw new IllegalArgumentException(); int leftSize = (t.left != null) ? ((BinaryNodeWithSize<T>) t.left).size : 0; if(k <= leftSize) return findKth(k, t.left); if(k == leftSize + 1) return t; return findKth(k - leftSize - 1, t.right); }
总结
二叉搜索树的定义使得查找和插入元素的时候可以按照一个类似于二分法的思路去操作节点。在它的基础上衍生出来的计算节点和各种插入删除操作都比较常见,这里对他们的过程做一个简单的整理。里面还有一些结合指向父节点的小细节的地方考虑还不够成熟。
相关推荐
二叉搜索树(Binary Search Tree, BST)是一种特殊的二叉树,其中每个节点的左子树只包含比它小的节点,右子树只包含比它大的节点。这种特性使得搜索、插入和删除操作的时间复杂度可以达到O(log n)。二叉搜索树的...
二叉搜索树(Binary Search Tree, BST)是一种非常重要的数据结构,其核心特性在于能够快速地进行查找、插入以及删除操作。在二叉搜索树中,每个节点包含一个键值(通常是一个整数或者浮点数),并且满足以下条件: 1...
二叉树是计算机科学中一种重要的数据结构,...总之,理解和掌握二叉树及其变种,尤其是二叉搜索树,是成为一名优秀程序员的关键技能之一。通过熟悉上述知识点和解题策略,能够有效应对面试中可能出现的二叉树相关问题。
二叉搜索树是一种二叉树,其中每个节点包含一个键(key)、一个关联值、一个指向左子树的引用和一个指向右子树的引用。二叉搜索树的性质规定,对于任意节点,其左子树中的所有节点的键都小于该节点的键,而右子树中...
二叉搜索树(Binary Search Tree,BST)是一种特殊的二叉树结构,它的每个节点都具有以下特性:左子树上所有节点的值均小于当前节点的值,右子树上所有节点的值均大于当前节点的值。这种特性使得二叉搜索树在查找、...
本实验——“数据结构二叉树实验——构造二叉搜索树”,旨在深入理解并实践二叉搜索树的构建过程和特性。 二叉搜索树是一种特殊的二叉树,每个节点都满足以下性质:左子树上的所有节点的键值小于父节点的键值;右子...
二叉搜索树(Binary Search Tree,BST),是数据结构领域中的一个重要概念,它是一种特殊的二叉树,每个节点的左子树只包含比其小的元素,而右子树则包含大于它的元素。这种特性使得二叉搜索树在查找、插入和删除...
二叉搜索树是一种特殊的二叉树,其中每个节点的左子树只包含比该节点值小的元素,而右子树包含的元素都大于该节点值。这样的特性使得BST非常适合进行查找、插入和删除等操作,因为它们的时间复杂度可以达到O(log n)...
二叉搜索树(Binary Search Tree,BST)是数据结构中的一种特殊类型,它是一种二叉树,具有以下特性: 1. 每个节点都包含一个键(key)、一个关联的值、一个指向左子节点的引用和一个指向右子节点的引用。 2. 节点...
二叉搜索树(Binary Search Tree,BST)是一种特殊类型的二叉树,它的每个节点都具有以下特性: 1. **左子树性质**:左子树上所有节点的值都小于当前节点的值。 2. **右子树性质**:右子树上所有节点的值都大于当前...
二叉排序树(Binary Sort Tree,BST),也称为二叉搜索树,是一种特殊的二叉树数据结构,它的每个节点都包含一个键(key)、一个关联的值、一个指向左子节点的指针和一个指向右子节点的指针。在二叉排序树中,对于...
二叉搜索树(Binary Search Tree,BST)是一种特殊类型的二叉树,它的每个节点都包含一个键(key)、一个关联的值、一个指向左子树的指针和一个指向右子树的指针。在二叉搜索树中,对于任何一个节点,其左子树中的...
在IT领域,二叉搜索树(Binary Search Tree, BST)是一种常见的数据结构,它具有左子树上所有节点的值小于根节点的值,右子树上所有节点的值大于根节点的值的特性。这样的特性使得二叉搜索树非常适合进行查找、插入...
接着,判断一棵二叉树是否为二叉排序树,可以通过递归检查每个节点是否满足上述二叉排序树的性质。对于每个节点,我们需要确保它的左子树中的所有节点的值都小于它,右子树中的所有节点的值都大于它,并且它的左右...
二叉搜索树(Binary Search Tree,BST)是一种特殊的二叉树结构,它的每个节点都包含一个键值(key)、一个关联的数据值、一个指向左子树的指针和一个指向右子树的指针。在二叉搜索树中,对于任意节点而言,其左子树...
二叉搜索树(Binary Search Tree,BST)是一种特殊的二叉树数据结构,它的每个节点都包含一个键(key)、一个关联的值、一个指向左子树的引用和一个指向右子树的引用。在二叉搜索树中,对于任意节点,其左子树中的...
二叉搜索树(Binary Search Tree, BST)是一种特殊类型的二叉树,它的每个节点都包含一个键(key)、一个关联的值、一个指向左子树的指针和一个指向右子树的指针。在二叉搜索树中,对于任何一个节点,其左子树中的...
二叉搜索树(Binary Search Tree,BST)是一种特殊的二叉树结构,它的每个节点都包含一个键(key)、一个关联的值、一个指向左子树的引用和一个指向右子树的引用。在二叉搜索树中,对于任意节点,其左子树中的所有...
二叉搜索树(Binary Search Tree, BST)是一种特殊的二叉树结构,它的每个节点都包含一个键(key)、一个关联的值、一个指向左子树的指针和一个指向右子树的指针。在二叉搜索树中,对于任何节点,其左子树中的所有...
二叉搜索树(Binary Search Tree,BST)是二叉树的一个特例,它具有以下特性:对于任意节点,其左子树中的所有节点的值都小于该节点的值,而右子树中的所有节点的值都大于该节点的值。这种特性使得二叉搜索树非常...