您的位置:首页 > Web前端

读剑指offer有感--搜索二叉树转化为有序双向链表

2015-06-04 19:51 549 查看

题目

输入一颗二查搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
建议根据书中附带的代码来跑程序,建树和输出树的部分就可以不用自己写了,具体代码可以到CSDN博客资源里边去搜索,有挺多免费资源的啦


题目分析

对于如下图的一颗二叉搜索树,经过转化后会变为





二叉搜索树有这个特性:一个节点左子树上所有节点的值都比这个节点值要小,右子树上所有节点的值都比这个节点值要大,所以它的中序遍历得到的输出序列便是一个已经从小到大排好序的序列。
树的中序遍历过程大致如下:
void inorderTraversal(BinaryTreeNode *node)
{
if (NULL != node->leftChild)
{
inorderTraversal(node->leftChild);
}
//对node节点做些事情
if (NULL != node->rightChild)
{
inorderTraversal(node->rightChild);
}
}


递归解法

按照上面的中序遍历方法,问题便转换为对node节点做什么事情?

6

/\

4 8

对于这样一颗只有3个节点的树,在6节点上会做什么呢?
4->rightChild = 6;
6->leftChild = 8;
也即6的左子树处提供双向链表中6节点之前的一个节点,
而6则为右子树的双向链表提供它们之前的一个节点。
所以可以用一个BinaryTreeNode节点pLastNodeInList

首先初始化为空,作为参数传入左子树,在左子树这里结束之后记录左子树生成的双向链表中最后一个节点
按照中序遍历规则,左子树生成的双向链表中最后一个节点应该是node节点的前一个节点,所以有pLastNodeInList->rightChild = node;node->leftChild = pLastNodeInList
按照中序遍历规则,node节点应该为右子树生成的双向链表的前一个节点,所以将pLastNodeInList记录为node,之后进入右子树递归处理

所以递归函数应该有两个参数,一个是node节点,另一个则是pLastNodeInList节点记录node节点前面一个节点,即:
void ConvertNode(BinaryTreeNode *pNode, BinaryTreeNode *&pLastNodeInList)

开始函数则是从root节点开始,也即:
BinaryTreeNode *Convert(BinaryTreeNode *pRoot)。

所以完整递归代码如下:
书中代码用的是BinaryTreeNode **pLastNodeInList,不过我不是很喜欢两重指针,所以用指针引用的方式。
需要注意的地方还有:

pNode节点特殊情况的考虑,即为空指针
pLastNodeInList在最初会初始化为NULL指针(因为整棵树的最左叶子节点是双向链表第一个节点,它前面一个节点自然为空),所以在使用pLastNodeInList->m_pRight的时候不要忘记考虑它为空的情况
从根节点进入的递归函数返回的pLastNodeInList记录的是双向节点最后一个节点的位置,要通过它移动到双向链表的头指针再返回

void ConvertNode(BinaryTreeNode *pNode, BinaryTreeNode *&pLastNodeInList)
{
if (NULL == pNode)
{
return;
}
BinaryTreeNode *pCurrent = pNode;
if (NULL != pNode->m_pLeft)
{
ConvertNode(pNode->m_pLeft, pLastNodeInList);
}
pCurrent->m_pLeft = pLastNodeInList;
if (NULL != pLastNodeInList)
{
pLastNodeInList->m_pRight = pCurrent;
}
pLastNodeInList = pCurrent;
if (NULL != pNode->m_pRight)
{
ConvertNode(pNode->m_pRight, pLastNodeInList);
}
}
BinaryTreeNode *Convert(BinaryTreeNode *pRoot)
{
BinaryTreeNode *pLastNodeInList = NULL;
ConvertNode(pRoot, pLastNodeInList);
BinaryTreeNode *pHeadOfList = pLastNodeInList;
while(NULL != pHeadOfList && NULL !=pHeadOfList->m_pLeft )
{
pHeadOfList = pHeadOfList->m_pLeft;
}
return pHeadOfList;
}


非递归解法

树的中序遍历是可以将递归方式改为非递归的,只需要用一个栈stack就可以,大致过程如下:
void inorderNoRecursiveTraversal(BinaryTreeNode *node)
{
Node *pNode = node;

while (NULL != pNode)
{

//1找最左子女节点
while (NULL != pNode->leftChild)
{
将pNode入栈
pNode = pNode->leftChild;
}
//2找到最左子女pNode了,对它进行某种操作
//3如果pNode有右子女,进入右子女节点
//4否则节点pTem出栈
//5对pTem进行某种操作
//6如果pTem有右子女,进入它的右子女,
//否则继续出栈,重复4-5步骤,直到有第6步执行,或者栈为空则结束
}
}
非递归步骤需要做的就是考虑清楚所有可能出现的情况,每种情况都要将所有可能的分支情况考虑好,循环的设定与结束也要想清楚,根据上面的框架,可以得到:

首先根据pNode节点找最左子女节点
找到最左子女pNode之后,对它进行某种操作
如果pNode有右子女,进入右子女节点
否则节点pTem出栈
对pTem进行某种操作
如果pTem有右子女,进入它的右子女,
否则继续出栈,重复4-5步骤,直到有第6步执行,或者栈为空则结束

在对最左子女pNode进行处理之后,有两条分支,一条是它有右子女,另一条则是它没右子女;
在出栈操作之后,对于出栈节点pTem,有两条分支,一条是它有右子女,另一条则是它没右子女。

最后代码可以写为:
BinaryTreeNode *ConvertNodeNoRecursive(BinaryTreeNode *pRootOfTree)
{
if (NULL == pRootOfTree)
{
return NULL;
}
stack<BinaryTreeNode *> stkNodes;
BinaryTreeNode *pPrev = NULL;
BinaryTreeNode *pCurrent = pRootOfTree;
while (NULL != pCurrent)
{
//1.找最左节点
while (NULL != pCurrent->m_pLeft)
{
//2.中途将所有父节点都加入栈中
stkNodes.push(pCurrent);
pCurrent = pCurrent->m_pLeft;
}
//

//3.找到最左节点之后,首先判断prev有没有,如果有则可以连接,
if (NULL != pPrev)
{
pPrev->m_pRight = pCurrent;
pCurrent->m_pLeft = pPrev;
}
pPrev = pCurrent;
//接着确定它的下一个节点,连接它们
//4.如果这个最左节点有右子女节点,进入右子女节点,之后会循环继续找最左
if (NULL != pCurrent->m_pRight)
{
pCurrent = pCurrent->m_pRight;
}
//5.如果最左节点没有右子女,那么可以出栈,并且改变出栈节点的m_pLeft连接
else
{
//如果栈中已经没有元素了,则循环可以结束了

while (!stkNodes.empty())
{
pCurrent = stkNodes.top();
stkNodes.pop();
pPrev->m_pRight = pCurrent;
pCurrent->m_pLeft = pPrev;
pPrev = pCurrent;
if (NULL != pCurrent->m_pRight)
{
pCurrent = pCurrent->m_pRight;
break;
}
}
if (stkNodes.empty())
{
break;
}
//
}

}
return pCurrent;
}
BinaryTreeNode* ConvertNoRecursive(BinaryTreeNode* pRootOfTree)
{
BinaryTreeNode *pLastNodeInList = ConvertNodeNoRecursive(pRootOfTree);

// pLastNodeInList指向双向链表的尾结点,
// 我们需要返回头结点
BinaryTreeNode *pHeadOfList = pLastNodeInList;
while(pHeadOfList != NULL && pHeadOfList->m_pLeft != NULL)
pHeadOfList = pHeadOfList->m_pLeft;

return pHeadOfList;
}

各种可能输入情况思考:

对于一颗二叉搜索树,会有一些什么样的情况呢?
首先是正常的二叉树,
接着是比较极端的树,也即左子女一路走到底,每个节点都没有右子女;
同理只有右子女的树。
还有空树,一个节点的树。





内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: