二叉搜索树的中序遍历是升序的。那么现在给你树中的任意一个结点,要你求这个结点的前驱和后继。你该怎么搞?
这个问题不是那么简单的,分下面几种情况
- 结点数据结构是否包含父节点的指针
- 有没有给你根节点
如果结点的数据结构没有给父节点指针也没有告诉你根节点,那么是不可能求出该结点的前序与后继的;其次结构中包含父节点指针,那么也就相当于给了你根节点。这里我们主要讨论结点中不包含父节点指针但是告诉你根节点的情况和结点中包含父节点指针没告诉你根节点的情况。
数据结构中没有父节点但是知道根节点
这个就太简单了,你只需要在二叉树的中序遍历中加入一个指针,这个指针指向当前结点的前一个结点。遍历过程中更新判断就行了。
例子:
这里使用二叉树的递归中序遍历,可以参考二叉树的遍历,也可以使用二叉树的非递归中序遍历,可以参考二叉树前序、中序、后序遍历的非递归写法。最后,前驱和后继分别存在std::pair::first
和std::pair::second
中。
void GetPrevNext(struct TreeNode *tag, struct TreeNode *now, struct TreeNode *pre, std::pair<TreeNode *, TreeNode *> & ans)
{
if (pre == tag)
{
ans.second = now;
return ;
}
GetPrevNext(tag, now->left, pre, ans);
if (now == tag)
{
ans.first = pre;
}
GetPrevNext(tag, now->right, pre = now, ans);
}
数据结构中有父节点指针
这个也比较好考虑,首先,中序遍历,先遍历左子树再遍历根最后遍历右子树,递归下去。那么求一个结点的前驱,根据中序遍历的顺序,前驱结点一定在其左子树中,后继结点一定在其右子树中。
这里和二叉排序树删除某一指定结点的思想是一样的,在那边博客中说了,删除根节点,就要在左子树中找一个最大的结点或者在右子树中找一个最小的结点来替换根节点。这里所找到的结点就是根节点中序遍历的前驱和后继,至于怎么在这种情况下找前驱和后继就参考二叉排序树删除某一指定结点这篇博文。不过这里还要考虑没有左子树和没有右子树的情况,这也是为什么需要父节点指针的原因。
其实没有左子树和没有右子树的情况也很好分析,依据还是中序遍历的遍历顺序。这里以查找前驱,但是没有左子树的情况为例。看下图:
对于这种情况,我们要反过来想,访问到26这个结点前,它访问的最后一个结点是谁,依次从26先上推:26是31的左子树,按照中序遍历的顺序,必然是先访问26,因此31不对;同理,33也不对;26是4的右子树的一部分,因此肯定是先访问了4然后再访问了26。于是,26的前驱结点找到了,就是4。
那么在找的时候我们就发现了寻找前驱结点但是该结点没有左子树的情况的规律:沿着目标结点的父节点向上找,直到找到一个结点,这个结点的右子树包含目标结点。这个规律的依据就是中序遍历的顺序。
同理,寻找后继结点但是该结点没有右子树的情况的规律:沿着目标结点的父节点向上找,直到找到一个结点,这个结点的左子树包含目标结点。
代码实现就很简单了,这里分别给出求前驱结点和求后继结点的代码。
先看下结点的数据结构:
template<typename T>
struct TreeNode
{
typedef TreeNode<T> type;
typedef type *ptr;
T data;
ptr father;
ptr left, right;
};
前驱
template<typename T>
typename TreeNode<T>::ptr GetPrevNode(typename TreeNode<T>::ptr root)
{
typedef typename TreeNode<T>::ptr ptr;
if (root == nullptr)
return nullptr;
ptr ret = nullptr;
if (root->left == nullptr) {
ret = root;
for (; ret->father != nullptr && ret->father->left == ret; ret = ret->father) {}
ret = ret->father;
} else {
ret = root->left;
for (ptr p = ret->right; p != nullptr; ret = p, p = p->right) {}
}
return ret;
}
后继
template<typename T>
typename TreeNode<T>::ptr GetNextNode(typename TreeNode<T>::ptr root)
{
typedef typename TreeNode<T>::ptr ptr;
if (root == nullptr)
return nullptr;
ptr ret = nullptr;
if (root->right == nullptr) {
ret = root;
for (; ret->father != nullptr && ret->father->right == ret; ret = ret->father) {}
ret = ret->father;
} else {
ret = root->right;
for (ptr p = ret->left; p != nullptr; ret = p, p = p->left) {}
}
return ret;
}
总结
二叉搜索树求前驱和求后继有什么用呢?迭代器,没错,就是这个,STL的几大组成部件之一,如果你要设计一种二叉搜索树的数据结构,也就是一个容器,你就必须提供一个迭代器,而迭代器的作用就是遍历容器,自然而然就会有向前和向后的操作,于是求前驱和求后继就变成了刚需。
同时,可以看到,求前驱和求后继的时间复杂度和树高有关系,也就是说如果是平衡二叉搜索树,那么求前驱和求后继的时间复杂度为 。