求二叉搜索树任一节点的前驱后继节点

二叉搜索树节点的前驱后继节点

之前写过文章介绍了二叉搜索树以及其上的基本操作,但不包括求节点的前驱结点和后继节点。

这是一个很老的问题了,首先看下某节点前驱和后继节点的定义。一个节点的
前驱结点:节点val值小于该节点val值并且值最大的节点
后继节点:节点val值大于该节点val值并且值最小的节点

算法导论中给出了详细的求前驱结点和后继节点的算法,但是其中的节点数据结构包含了指向父亲节点的指针,但是一般的给出的节点不包含父亲指针,这就加大了就前驱节点和后继节点的难度。

本文在不含父指针的节点数据结构下,分析给出了时间复杂度为O(lgN)的求前驱后继结点的算法。


例子

树节点的依旧定义如下(我们的基本树节点没有指向父节点的指针):

struct TreeNode {
      int val;
      TreeNode *left;
      TreeNode *right;
      TreeNode(int x) : val(x), left(NULL), right(NULL) {}
  };

给出一个二叉树如下图:

求二叉搜索树任一节点的前驱后继节点_第1张图片

二叉树的节点val值是按照二叉树中序遍历顺序连续设定。

前驱结点

  • 如图4的前驱结点是3
  • 2的前驱结点是1
  • 6的前驱结点是5

后继节点

  • 7的后继结点是8
  • 5的后继节点是6
  • 2的后继节点是3

规则

根据上述例子,我们可以得到下述规则:

前驱节点

  1. 若一个节点有左子树,那么该节点的前驱节点是其左子树中val值最大的节点(也就是左子树中所谓的rightMostNode)
  2. 若一个节点没有左子树,那么判断该节点和其父节点的关系
    2.1 若该节点是其父节点的右边孩子,那么该节点的前驱结点即为其父节点。
    2.2 若该节点是其父节点的左边孩子,那么需要沿着其父亲节点一直向树的顶端寻找,直到找到一个节点P,P节点是其父节点Q的右边孩子(可参考例子2的前驱结点是1),那么Q就是该节点的后继节点

类似,我么可以得到求后继节点的规则。

后继节点

  1. 若一个节点有右子树,那么该节点的后继节点是其右子树中val值最小的节点(也就是右子树中所谓的leftMostNode)
  2. 若一个节点没有右子树,那么判断该节点和其父节点的关系
    2.1 若该节点是其父节点的左边孩子,那么该节点的后继结点即为其父节点
    2.2 若该节点是其父节点的右边孩子,那么需要沿着其父亲节点一直向树的顶端寻找,直到找到一个节点P,P节点是其父节点Q的左边孩子(可参考例子2的前驱结点是1),那么Q就是该节点的后继节点

实现

求前驱节点

规则中我们是从下往上找,但实际代码中是不允许我们这么操作的(由于我们没有父亲指针),我们可以在寻找对应val节点的过程中从上向下找,并且过程中记录下parent节点和firstRParent节点(最后一次在查找路径中出现右拐的节点)。
实现如下:

TreeNode* getRightNode(TreeNode* root)
{
  if(root ==NULL)  return NULL;
  while(root->right !=NULL)
      root = root->right;
  return root;
}


TreeNode* getPNode(TreeNode* root,int value,TreeNode*& parent,TreeNode*& firstRParent)
{
    while(root)
    {
        if(root->val == value)
            return root;

        parent = root;
        if(root->val>value)
        {
            root = root->left;
        }else{
            firstRParent = root;//出现右拐点
            root = root->right;
        }
    }

    return NULL;
}
//主函数
TreeNode*  getPreNode(TreeNode* root, int value)
{
     if(root)
     {
           TreeNode* parent =NULL;
           TreeNode* firstRParent =NULL;

          TreeNode* node = getPNode(root,value,parent ,firstRParent );
          if(node == NULL)
                  return node;
          if(node->left) //有左子树
             return getRightNode(node->right);

          if(NULL == parent ||(parent && (NULL == firstRParent))) return NULL; //没有前驱节点的情况

          if(node == parent->right) //没有左子树 是其父节点的右边节点 
              return parent;
          else//没有左子树 是其父节点的左边节点 
           {
               return firstRParent ;
           }

     }
    return root;
}

求后继节点

同样,求后继节点我们不能从底向上找,也是从上向下找,首先是找到对应val值的节点,顺便把其的parent节点和firstlParent节点(最后一次在查找路径中出现左拐的节点)。
实现如下:

TreeNode* getLeftNode(TreeNode* root)
{
  if(root ==NULL)  return NULL;
  while(root->left !=NULL)
      root = root->left;
  return root;
}

TreeNode* getNode(TreeNode* root,int value,TreeNode*& parent,TreeNode*& firstlParent)
{
    while(root)
    {
        if(root->val == value)
            return root;

        parent = root;//设置父亲节点
        if(root->val<value)
        {
            root = root->right;
        }else{
            firstlParent = root;//发生了左拐
            root = root->left;
        }
    }
    return NULL;
}

//主函数
TreeNode*  getNextNode(TreeNode* root, int value)
{
     if(root)
     {
           TreeNode* parent =NULL;
           TreeNode* firstlParent =NULL;

          TreeNode* node = getNode(root,value,parent ,firstlParent );
          if(node == NULL)
                  return node;
          if(node->right)//有右子树
             return getLeftNode(node->right);
          if(NULL == parent ||(parent && (NULL == firstlParent))) return NULL; //没有后继节点的情况
          if(node == parent->left)//没有右子树 是其父节点的左边节点 
              return parent;
          else//没有右子树 是其父节点的右边节点
           {
               return firstlParent ;
           }

     }
    return root;
}

总结

当然我们可以对一个二叉搜索树直接进行中序遍历,立马可以得到节点的前驱和后继节点,但是这样的方法时间复杂度为O(N),显然不是最好的方法。

本文讨论了在没有父亲指针的二叉树中如何查找一个节点的后继节点和前驱结点,算法的时间复杂度是O(lgN)。

你可能感兴趣的:(算法)