二叉搜索樹節點的前驅後繼節點
之前寫過文章介紹了二叉搜索樹以及其上的基本操作,但不包括求節點的前驅結點和後繼節點。
這是一個很老的問題了,首先看下某節點前驅和後繼節點的定義。一個節點的
前驅結點:節點val值小於該節點val值並且值最大的節點
後繼節點:節點val值大於該節點val值並且值最小的節點
算法導論中給出了詳細的求前驅結點和後繼節點的算法,但是其中的節點數據結構包含了指向父親節點的指針,但是一般的給出的節點不包含父親指針,這就加大了就前驅節點和後繼節點的難度。
本文在不含父指針的節點數據結構下,分析給出了時間複雜度爲O(lgN)的求前驅後繼結點的算法。
例子
樹節點的依舊定義如下(我們的基本樹節點沒有指向父節點的指針):
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
給出一個二叉樹如下圖:
二叉樹的節點val值是按照二叉樹中序遍歷順序連續設定。
前驅結點
- 如圖4的前驅結點是3
- 2的前驅結點是1
- 6的前驅結點是5
後繼節點
- 7的後繼結點是8
- 5的後繼節點是6
- 2的後繼節點是3
規則
根據上述例子,我們可以得到下述規則:
前驅節點
- 若一個節點有左子樹,那麼該節點的前驅節點是其左子樹中val值最大的節點(也就是左子樹中所謂的rightMostNode)
- 若一個節點沒有左子樹,那麼判斷該節點和其父節點的關係
2.1 若該節點是其父節點的右邊孩子,那麼該節點的前驅結點即爲其父節點。
2.2 若該節點是其父節點的左邊孩子,那麼需要沿着其父親節點一直向樹的頂端尋找,直到找到一個節點P,P節點是其父節點Q的右邊孩子(可參考例子2的前驅結點是1),那麼Q就是該節點的後繼節點
類似,我麼可以得到求後繼節點的規則。
後繼節點
- 若一個節點有右子樹,那麼該節點的後繼節點是其右子樹中val值最小的節點(也就是右子樹中所謂的leftMostNode)
- 若一個節點沒有右子樹,那麼判斷該節點和其父節點的關係
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)。