您的位置:首页 > 其它

重建二叉树

2016-04-27 10:52 281 查看
通常树有如下几种遍历方式:

1.前序遍历:先访问根结点,再访问左子结点,最后访问右子结点。

2.中序遍历:先访问左子结点,再访问根结点,最后访问右子结点。

3.后序遍历:先访问左子结点,再访问右子结点,最后访问根结点。

这三种都属于深度优先遍历的方式,与之对应的还有 宽度优先遍历,也叫广度优先遍历。

题目:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1, 2, 4, 7, 3, 5, 6, 8}和中序遍历序列{4, 7, 2, 1, 5, 3, 8, 6},则重建出二叉树如下图所示,并输出它的头结点。二叉树的结构定义如下:

typedef struct BinaryTreeNode
{
int value;
struct BinaryTreeNode *pleft;
struct BinaryTreeNode *pright;
}NODE, *PNODE;




由于在中序遍历中有3个数字是左子树结点的值,因此左子树总共有3个子结点。同样在前序遍历中,根结点之后的3个数字就是左子结点的值,再后面的数字都是右子树结点的值,。这样我们再前序遍历序列和中序遍历序列中分别找到了左右子树对应的子序列。



既然我们已经找到了左右子树的前序遍历序列和中序遍历序列,我们也就可以用同样的方法来构建去分别构建左右子树。也就是说,接下来的事情,我们可以用递归来完成:

PNODE  ConstructCore(int *pre_start, int *pre_end,
int *ino_start, int *ino_end)
{
//前序遍历第一个数字是根结点的值
int root_val = pre_start[0];
PNODE root = (PNODE)malloc(sizeof(NODE));
root->value = root_val;
root->pleft = root->pright = NULL;

if (pre_start == pre_end)
{
if (ino_start == ino_end && *pre_start == *ino_start)
return root;
else
exit(1);	//输入有误
}

//在中序遍历中找到根结点的值

int *ino_root = ino_start;
while (ino_root <= ino_end && *ino_root != root_val)
ino_root++;

if (ino_root == ino_end && *ino_root != root_val)
exit(1);		//输入有误

int len_left = ino_root - ino_start;
int *pre_end_left = pre_start + len_left;	//跳过最开始的根结点
if (len_left > 0)
{
//构建左子树
root->pleft = ConstructCore(pre_start + 1, pre_end_left,
ino_start, ino_root - 1);
}
if (len_left < pre_end - pre_start)
{
//构建右子树
root->pright = ConstructCore(pre_end_left + 1, pre_end,
ino_root + 1, ino_end);
}

return root;
}

PNODE Construct(int *preorder, int *inorder, int len)
{
if (preorder == NULL || inorder == NULL || len <= 0)
return NULL;

return ConstructCore(preorder, preorder + len - 1, inorder, inorder + len - 1);
}


为了最后能输出这颗二叉树以检验函数的正确性,我又实现了前序遍历二叉树:

#define OK 1
#define ERROR 0

int print(PNODE pnode)
{
printf("%d ", pnode->value);
return OK;
}

int pre_order_traverse(PNODE root, int (*visit)(PNODE pnode))
{
if (root)
{
if (visit(root))
{
if (pre_order_traverse(root->pleft, visit))
{
if(pre_order_traverse(root->pright, visit))
return OK;
}
}
return ERROR;
}
return OK;
}
当然既然用到了动态分配内存,你就要负责释放掉申请的内存,所以有了下面这段代码(后序遍历):

int free_node(PNODE pnode)
{
free(pnode);
pnode = NULL;
return OK;
}

int post_order_traverse(PNODE root, int (*visit)(PNODE pnode))
{
if (root)
{
if(post_order_traverse(root->pleft, visit))
{
if (post_order_traverse(root->pright, visit))
visit(root);
}
else
return ERROR;
}

return OK;
}
当然可以不用实现之前的前序遍历,用后序遍历来输出也是可以的。但是用前序遍历来做内存的释放就不行了,因为当你释放了一个结点后,还怎么去找它的左右孩子?有些人可能就要说了,free()之后指针的值并没有发生改变,还可以找的到!可我认为虽然还可以找的到,但这是不好的,所以我在free()之后加了给指针赋值为NULL的语句。

最后是测试用的main函数:

int main()
{
int pre[] = {1, 2, 4, 7, 3, 5, 6, 8};
int ino[] = {4, 7, 2, 1, 5, 3, 8, 6};

PNODE tree = Construct(pre, ino, 8);
pre_order_traverse(tree, print);
printf("\n");

post_order_traverse(tree, free_node);

return 0;
}


只是做了个简单的测试,并没有输入太多例子,有兴趣的可以自己实现一下。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: