给定一个二叉树,原地将它展开为一个单链表。
例如,给定二叉树
1
/ \
2 5
/ \ \
3 4 6
将其展开为:
1
\
2
\
3
\
4
\
5
\
6
我们不难发现这个链表展开的顺序就是二叉树的先序遍历,但是题目要求我们是原地展开,所以我们需要一个东西来储存二叉树的先序遍历顺序,然后再循环建立链表。
方法一
前序遍历
所以我们第一种实现函数如下,主要是利用指向结构体root类型的指针数组来实现。
int num;
void flatten(struct TreeNode* root){
num=0;//结点数量
struct TreeNode **l=(struct TreeNode **)malloc(0);//节点数量开始为0,后来根据结点的不同来扩大容量.
preOrderTravesal(root,&l);//先序访问结点,记录结点顺序并存储到指针数组中
for(int i=1;i<num;i++)
{
struct TreeNode *pre=l[i-1],*cur=l[i];
pre->left=NULL;
pre->right=cur;
}
free(l);//最后释放指针数字
}
void preOrderTravesal(struct TreeNode *root,struct TreeNode ***l)//***l是指向**l的指针数组
{
if(root)//树不为空的时候
{
num++;//树的结点+1
(*l)=(struct TreeNode**)realloc((*l),sizeof(struct TreeNode**)*num);//realloc函数是扩展malloc函数开辟的空间,我们具体根据Num的值来开辟空间
(*l)[num-1]=root;
preOrderTravesal(root->left,l);//递归访问左子树先序遍历
preOrderTravesal(root->right,l);//递归访问右子树先序遍历
}
}
最后得到的结果是这样:
我们不难发现,执行速度还是可以的,但是空间消耗太大了。由此我们需要再想一种更有效率的办法。
方法二
寻找前驱结点
我们对这道题目的具体做法是,对于当前节点,如果他的左子节点不为空,则在其左子树中找到最右边的节点,作为前驱的节点,将当前节点的右子节点赋给前驱节点的右子节点,然后将当前节点的左子节点赋给当前节点的右子节点,并将当前节点的左子节点设为空。对当前节点处理结束后,继续处理链表中的下一个节点,直到所有节点都处理结束。
执行代码如下:
void flatten(struct TreeNode* root){
struct TreeNode *current=root;//目前结点指向根节点
while(current!=NULL)//循环
{
if(current->left)//我们每次都去找当前结点的左子树,左子树有结点,再去找左子树的右子树,右子树的最后一个节点为前驱结点,若没有右子树,则左子树的最后一个节点为前驱结点。
{
struct TreeNode *next=current->left;
struct TreeNode *pre=next;
while(pre->right)
{
pre=pre->right;//递归找到前驱结点
}
pre->right=current->right;//将目前结点的右子树全部连接到前驱结点的右子树上去
current->right=next;//目前结点右子树连接目前结点的左子树
current->left=NULL;//目前结点左子树置为空
}
current=current->right;//递归找目前结点的右子树
}
}
结果如下