剑指Offer----面试题六:重建二叉树

来源:互联网 发布:太阳能电池计算软件 编辑:程序博客网 时间:2024/06/13 21:23

前言:

二叉树有多种遍历方式,这里主要讲其中的三种遍历方式六种实现方法(递归和非递归)。

递归方式且看后边源代码。

这里主要讲三种非递归实现方法。

以下内容引自<http://blog.csdn.net/pi9nc/article/details/13008511>

三种遍历方式的非递归实现

    为了便于理解,这里以下图的二叉树为例,分析二叉树的三种遍历方式的实现过程。


          1、前序遍历的非递归实现 

根据先序遍历的顺序,先访问根节点,再访问左子树,后访问右子树,而对于每个子树来说,又按照同样的访问顺序进行遍历,上图的先序遍历顺序为:ABDECF。非递归的实现思路如下:

对于任一节点P

1)输出节点P,然后将其入栈,再看P的左孩子是否为空;

2)P的左孩子不为空,则置P的左孩子为当前节点,重复1)的操作;

3)P的左孩子为空,则将栈顶节点出栈,但不输出,并将出栈节点的右孩子置为当前节点,看其是否为空;

4)若不为空,则循环至1)操作;

5)如果为空,则继续出栈,但不输出,同时将出栈节点的右孩子置为当前节点,看其是否为空,重复4)和5)操作;

6)直到当前节点PNULL并且栈空,遍历结束。

   

   下面以上图为例详细分析其先序遍历的非递归实现过程:

首先,从根节点A开始,根据操作1),输出A,并将其入栈,由于A的左孩子不为空,根据操作2),将B置为当前节点,再根据操作1),将B输出,并将其入栈,由于B的左孩子也不为空,根据操作2),将D置为当前节点,再根据操作1),输出D,并将其入栈,此时输出序列为ABD

由于D的左孩子为空,根据操作3),将栈顶节点D出栈,但不输出,并将其右孩子置为当前节点;

由于D的右孩子为空,根据操作5),继续将栈顶节点B出栈,但不输出,并将其右孩子置为当前节点;

由于B的右孩子E不为空,根据操作1),输出E,并将其入栈,此时输出序列为:ABDE

由于E的左孩子为空,根据操作3),将栈顶节点E出栈,但不输出,并将其右孩子置为当前节点;

由于E的右孩子为空,根据操作5),继续将栈顶节点A出栈,但不输出,并将其右孩子置为当前节点;

由于A的右孩子C不为空,根据操作1),输出C,并将其入栈,此时输出序列为:ABDEC

由于A的左孩子F不为空,根据操作2),则将F置为当前节点,再根据操作1),输出F,并将其入栈,此时输出序列为:ABDECF

由于F的左孩子为空,根据操作3),将栈顶节点F出栈,但不输出,并将其右孩子置为当前节点;

由于F的右孩子为空,根据操作5),继续将栈顶元素C出栈,但不输出,并将其右孩子置为当前节点;

此时栈空,且C的右孩子为NULL,因此遍历结束。


   根据以上思路,前序遍历的非递归实现代码如下:

void pre_traverse(BTree pTree){PSTACK stack = create_stack();  //创建一个空栈BTree node_pop;                 //用来保存出栈节点BTree pCur = pTree;             //定义用来指向当前访问的节点的指针//直到当前节点pCur为NULL且栈空时,循环结束while(pCur || !is_empty(stack)){//从根节点开始,输出当前节点,并将其入栈,//同时置其左孩子为当前节点,直至其没有左孩子,及当前节点为NULLprintf("%c ", pCur->data);push_stack(stack,pCur);pCur = pCur->pLchild;//如果当前节点pCur为NULL且栈不空,则将栈顶节点出栈,//同时置其右孩子为当前节点,循环判断,直至pCur不为空while(!pCur && !is_empty(stack)){pCur = getTop(stack);pop_stack(stack,&node_pop);pCur = pCur->pRchild;}}}


   2、中序遍历的非递归实现

根据中序遍历的顺序,先访问左子树,再访问根节点,后访问右子树,而对于每个子树来说,又按照同样的访问顺序进行遍历,上图的中序遍历顺序为:DBEAFC。非递归的实现思路如下:

对于任一节点P

1)P的左孩子不为空,则将P入栈并将P的左孩子置为当前节点,然后再对当前节点进行相同的处理;

2)P的左孩子为空,则输出P节点,而后将P的右孩子置为当前节点,看其是否为空;

3)若不为空,则重复1)和2)的操作;

4)若为空,则执行出栈操作,输出栈顶节点,并将出栈的节点的右孩子置为当前节点,看起是否为空,重复3)和4)的操作;

5)直到当前节点PNULL并且栈为空,则遍历结束。


   下面以上图为例详细分析其中序遍历的非递归实现过程:

首先,从根节点A开始,A的左孩子不为空,根据操作1)将A入栈,接着将B置为当前节点,B的左孩子也不为空,根据操作1),将B也入栈,接着将D置为当前节点,由于D的左子树为空,根据操作2),输出D

由于D的右孩子也为空,根据操作4),执行出栈操作,将栈顶结点B出栈,并将B置为当前节点,此时输出序列为DB

由于B的右孩子不为空,根据操作3),将其右孩子E置为当前节点,由于E的左孩子为空,根据操作1),输出E,此时输出序列为DBE

由于E的右孩子为空,根据操作4),执行出栈操作,将栈顶节点A出栈,并将节点A置为当前节点,此时输出序列为DBEA

此时栈为空,但当前节点A的右孩子并不为NULL,继续执行,由于A的右孩子不为空,根据操作3),将其右孩子C置为当前节点,由于C的左孩子不为空,根据操作1),将C入栈,将其左孩子F置为当前节点,由于F的左孩子为空,根据操作2),输出F,此时输出序列为:DBEAF

由于F的右孩子也为空,根据操作4),执行出栈操作,将栈顶元素C出栈,并将其置为当前节点,此时的输出序列为:DBEAFC

由于C的右孩子为NULL,且此时栈空,根据操作5),遍历结束。


    根据以上思路,中序遍历的非递归实现代码如下:

void in_traverse(BTree pTree){PSTACK stack = create_stack();  //创建一个空栈BTree node_pop;                 //用来保存出栈节点BTree pCur = pTree;             //定义指向当前访问的节点的指针//直到当前节点pCur为NULL且栈空时,循环结束while(pCur || !is_empty(stack)){if(pCur->pLchild){//如果pCur的左孩子不为空,则将其入栈,并置其左孩子为当前节点push_stack(stack,pCur);pCur = pCur->pLchild;}else{//如果pCur的左孩子为空,则输出pCur节点,并将其右孩子设为当前节点,看其是否为空printf("%c ", pCur->data);pCur = pCur->pRchild;//如果为空,且栈不空,则将栈顶节点出栈,并输出该节点,//同时将它的右孩子设为当前节点,继续判断,直到当前节点不为空while(!pCur && !is_empty(stack)){pCur = getTop(stack);printf("%c ",pCur->data);pop_stack(stack,&node_pop);pCur = pCur->pRchild;}}}}


   3、后序遍历的非递归实现

根据后序遍历的顺序,先访问左子树,再访问右子树,后访问根节点,而对于每个子树来说,又按照同样的访问顺序进行遍历,上图的后序遍历顺序为:DEBFCA。后序遍历的非递归的实现相对来说要难一些,要保证根节点在左子树和右子树被访问后才能访问,思路如下:

对于任一节点P

1)先将节点P入栈;

2)P不存在左孩子和右孩子,或者P存在左孩子或右孩子,但左右孩子已经被输出,则可以直接输出节点P,并将其出栈,将出栈节点P标记为上一个输出的节点,再将此时的栈顶结点设为当前节点;

3)若不满足2)中的条件,则将P的右孩子和左孩子依次入栈,当前节点重新置为栈顶结点,之后重复操作2);

4)直到栈空,遍历结束。


   下面以上图为例详细分析其后序遍历的非递归实现过程:

首先,设置两个指针:Cur指针指向当前访问的节点,它一直指向栈顶节点,每次出栈一个节点后,将其重新置为栈顶结点,Pre节点指向上一个访问的节点;

Cur首先指向根节点APre先设为NULL,由于A存在左孩子和右孩子,根据操作3),先将右孩子C入栈,再将左孩子B入栈,Cur改为指向栈顶结点B

由于B的也有左孩子和右孩子,根据操作3),将ED依次入栈,Cur改为指向栈顶结点D

由于D没有左孩子,也没有右孩子,根据操作2),直接输出D,并将其出栈,将Pre指向DCur指向栈顶结点E,此时输出序列为:D

由于E也没有左右孩子,根据操作2),输出E,并将其出栈,将Pre指向ECur指向栈顶结点B,此时输出序列为:DE

由于B的左右孩子已经被输出,即满足条件Pre==Cur->lchildPre==Cur->rchild,根据操作2),输出B,并将其出栈,将Pre指向BCur指向栈顶结点C,此时输出序列为:DEB

由于C有左孩子,根据操作3),将其入栈,Cur指向栈顶节点F

由于F没有左右孩子,根据操作2),输出F,并将其出栈,将Pre指向FCur指向栈顶结点C,此时输出序列为:DEBF

由于C的左孩子已经被输出,即满足Pre==Cur->lchild,根据操作2),输出C,并将其出栈,将Pre指向CCur指向栈顶结点A,此时输出序列为:DEBFC

由于A的左右孩子已经被输出,根据操作2),输出A,并将其出栈,此时输出序列为:DEBFCA

此时栈空,遍历结束。


   根据以上思路,后序遍历的非递归实现代码如下:

void beh_traverse(BTree pTree){PSTACK stack = create_stack();  //创建一个空栈BTree node_pop;          //用来保存出栈的节点BTree pCur;              //定义指针,指向当前节点BTree pPre = NULL;       //定义指针,指向上一各访问的节点//先将树的根节点入栈push_stack(stack,pTree);  //直到栈空时,结束循环while(!is_empty(stack)){pCur = getTop(stack);   //当前节点置为栈顶节点if((pCur->pLchild==NULL && pCur->pRchild==NULL) || (pPre!=NULL && (pCur->pLchild==pPre || pCur->pRchild==pPre))){//如果当前节点没有左右孩子,或者有左孩子或有孩子,但已经被访问输出,//则直接输出该节点,将其出栈,将其设为上一个访问的节点printf("%c ", pCur->data);pop_stack(stack,&node_pop);pPre = pCur;}else{//如果不满足上面两种情况,则将其右孩子左孩子依次入栈if(pCur->pRchild != NULL)push_stack(stack,pCur->pRchild);if(pCur->pLchild != NULL)push_stack(stack,pCur->pLchild);}}}


    以上遍历算法在VC上实现的输出结果如下:



二 面试题

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


分析:


如上图所示,前序遍历的第一个数字就是根节点,在中序遍历序列中查找该根节点,该(中序遍历)根节点左边的数字就是该根节点的左子树并记录个数L1,该(中序遍历)根结点的右边就是该根节点的右子树个数R1;紧跟在前序遍历后边的L1个数是前序遍历序列的左子树个数,同理R1为右孩子个数,在前序遍历序列左子树个数中的第一个为左子树的根节点,同上述方法在中序遍历中查找根节点....所以可以使用递归的方式实现该算法。

源代码如下:
BinaryTreeNode * Construct(int *preorder, int *inorder, int length){if (preorder == NULL || inorder == NULL || length <= 0)return NULL;elsereturn ConstructNode(preorder, preorder + length - 1, inorder, inorder + length - 1);}BinaryTreeNode * ConstructNode(int *startPreorder, int *endPreorder, int *startInorder, int *endInorder){//前序遍历序列的第一个数字是根结点的值int rootValue = startPreorder[0];BinaryTreeNode *root = new BinaryTreeNode();root->element = rootValue;root->left = root->right = NULL;if (startPreorder == endPreorder){if ((startInorder == endInorder) && (*startPreorder == *endPreorder))return root;elsethrow std::exception("Invalid input.");}//在中序遍历中找到根节点的值int *rootInorder = startInorder;while (rootInorder < endInorder && (*rootInorder != rootValue))++rootInorder;if ((rootInorder == endInorder) && (*rootInorder != rootValue))throw std::exception("Invalid input.");int leftLength = rootInorder - startInorder;int *leftPreorderEnd = startPreorder + leftLength;if (leftLength > 0)//构建左子树root->left = ConstructNode(startPreorder + 1, leftPreorderEnd, startInorder, rootInorder - 1);if (leftLength < endPreorder - startPreorder)//构建右子树root->right = ConstructNode(leftPreorderEnd + 1, endPreorder, rootInorder + 1, endInorder);return root;}



官方源代码(有更改)

头文件BinaryTree.h

#ifndef BINARY_TREE_N#define BINARY_TREE_Nnamespace OrdinaryBinaryTreeSpace{struct BinaryTreeNode{int element;BinaryTreeNode *left;BinaryTreeNode *right;};BinaryTreeNode * CreateBinaryTreeNode(int value);void ConnectBinaryTreeNodes(BinaryTreeNode *pParent, BinaryTreeNode *left, BinaryTreeNode *right);void PrintTreeNode(BinaryTreeNode * pNode);void PrintTreePre(BinaryTreeNode *pRoot);void PrintTreeMid(BinaryTreeNode *pRoot);void PrintTreeLast(BinaryTreeNode *pRoot);void PrintTreePreNoRec(BinaryTreeNode *pHead);void PrintTreeMidNoRec(BinaryTreeNode *pHead);void PrintTreeLastNoRec(BinaryTreeNode *pHead);void DestoryTree(BinaryTreeNode *pRoot);BinaryTreeNode * Construct(int *preorder, int *inorder, int length);BinaryTreeNode * ConstructNode(int *startPreorder, int *endPreorder, int *startInorder, int *endInorder);}#endif

源文件BinaryTree.cpp
#include"BinaryTree.h"#include<iostream>#include<stack>namespace OrdinaryBinaryTreeSpace{BinaryTreeNode * CreateBinaryTreeNode(int value){BinaryTreeNode *pNew = new BinaryTreeNode();pNew->element = value;pNew->left = NULL;pNew->right = NULL;return pNew;}void ConnectBinaryTreeNodes(BinaryTreeNode *pParent, BinaryTreeNode *left, BinaryTreeNode *right){if (pParent != NULL){pParent->left = left;pParent->right = right;}}void PrintTreeNode(BinaryTreeNode * pNode){if (pNode != NULL){std::cout << "the root value is " << pNode->element << std::endl;if (pNode->left != NULL)std::cout << "the left child value is " << pNode->left->element << std::endl;elsestd::cout << "the left child is empty" << std::endl;if (pNode->right != NULL)std::cout << "the right child value is " << pNode->right->element << std::endl;elsestd::cout << "the right child is empty" << std::endl;}}//前序遍历递归打印void PrintTreePre(BinaryTreeNode *pRoot){if (pRoot == NULL)std::cout << "The tree is empty" << std::endl;else{std::cout << pRoot->element << "  ";if (pRoot->left != NULL)PrintTreePre(pRoot->left);if (pRoot->right != NULL)PrintTreePre(pRoot->right);}}/*前序遍历的非递归实现:对于任一节点P,1)输出节点P,然后将其入栈,再看P的左孩子是否为空;2)若P的左孩子不为空,则置P的左孩子为当前节点,重复1)的操作;3)若P的左孩子为空,则将栈顶节点出栈,但不输出,并将出栈节点的右孩子置为当前节点,看其是否为空;4)若不为空,则循环至1)操作;5)如果为空,则继续出栈,但不输出,同时将出栈节点的右孩子置为当前节点,看其是否为空,重复4)和5)操作;6)直到当前节点P为NULL并且栈空,遍历结束。*///前序遍历的非递归实现void PrintTreePreNoRec(BinaryTreeNode *pHead){if (pHead == NULL){std::cout << "The tree is empty" << std::endl;return;}std::stack<BinaryTreeNode *> st;BinaryTreeNode *pCur = pHead;//定义用来指向当前访问结点的指针//直到当前结点为空或者栈为空时,循环结束while (pCur || !st.empty()){//从根结点开始,输出当前结点,并将其入栈//同时置其左孩子为当前结点,直至其没有左孩子std::cout << pCur->element << "  ";st.push(pCur);pCur = pCur->left;//如果当前结点pCur为空且栈不为空,则将栈顶结点出栈但不输出//同时值右孩子为当前结点,循环判断,直至pCur不为空while (pCur == NULL && !st.empty()){pCur = st.top();st.pop();pCur = pCur->right;}}}//中序遍历递归打印void PrintTreeMid(BinaryTreeNode *pRoot){if (pRoot == NULL)std::cout << "The tree is empty" << std::endl;else{if (pRoot->left != NULL)PrintTreeMid(pRoot->left);std::cout << pRoot->element << "  ";if (pRoot->right != NULL)PrintTreeMid(pRoot->right);}}//中序遍历的非递归实现void PrintTreeMidNoRec(BinaryTreeNode *pHead){if (pHead == NULL){std::cout << "The tree is empty" << std::endl;return;}std::stack<BinaryTreeNode *> st;BinaryTreeNode *pCur = pHead;while (pCur || !st.empty()){if (pCur->left != NULL){st.push(pCur);pCur = pCur->left;}else{std::cout << pCur->element << "  ";pCur = pCur->right;if (pCur == NULL && !st.empty()){pCur = st.top();st.pop();std::cout << pCur->element << "  ";pCur = pCur->right;}}}}//后序遍历递归打印void PrintTreeLast(BinaryTreeNode *pRoot){if (pRoot == NULL)std::cout << "The tree is empty" << std::endl;else{if (pRoot->left != NULL)PrintTreeLast(pRoot->left);if (pRoot->right != NULL)PrintTreeLast(pRoot->right);std::cout << pRoot->element << "  ";}}//后序遍历的非递归实现void PrintTreeLastNoRec(BinaryTreeNode *pHead){if (pHead == NULL){std::cout << "The tree is empty" << std::endl;return;}BinaryTreeNode *pCur = pHead;BinaryTreeNode *pPre = NULL;//指向上一个访问过的结点std::stack<BinaryTreeNode *> st;st.push(pHead);//先将树的根节点入栈while (!st.empty()){pCur = st.top();//当前结点置为栈顶结点if ((pCur->left == NULL && pCur->right == NULL) || (pPre != NULL && (pCur->left == pPre || pCur->right == pPre))){//如果当前结点没有左右孩子,或者有左孩子或右孩子,但已经被访问输出//则直接输出该结点,将其出栈,将设其为上一个访问结点std::cout << pCur->element << "  ";st.pop();pPre = pCur;}else{//如果不满足以上两种情况,则将其左孩子右孩子依次入栈if (pCur->right != NULL)st.push(pCur->right);if (pCur->left != NULL)st.push(pCur->left);}}}void DestoryTree(BinaryTreeNode *pRoot){if (pRoot != NULL){DestoryTree(pRoot->left);DestoryTree(pRoot->right);delete pRoot;}}BinaryTreeNode * Construct(int *preorder, int *inorder, int length){if (preorder == NULL || inorder == NULL || length <= 0)return NULL;elsereturn ConstructNode(preorder, preorder + length - 1, inorder, inorder + length - 1);}BinaryTreeNode * ConstructNode(int *startPreorder, int *endPreorder, int *startInorder, int *endInorder){//前序遍历序列的第一个数字是根结点的值int rootValue = startPreorder[0];BinaryTreeNode *root = new BinaryTreeNode();root->element = rootValue;root->left = root->right = NULL;if (startPreorder == endPreorder){if ((startInorder == endInorder) && (*startPreorder == *endPreorder))return root;elsethrow std::exception("Invalid input.");}//在中序遍历中找到根节点的值int *rootInorder = startInorder;while (rootInorder < endInorder && (*rootInorder != rootValue))++rootInorder;if ((rootInorder == endInorder) && (*rootInorder != rootValue))throw std::exception("Invalid input.");int leftLength = rootInorder - startInorder;int *leftPreorderEnd = startPreorder + leftLength;if (leftLength > 0)//构建左子树root->left = ConstructNode(startPreorder + 1, leftPreorderEnd, startInorder, rootInorder - 1);if (leftLength < endPreorder - startPreorder)//构建右子树root->right = ConstructNode(leftPreorderEnd + 1, endPreorder, rootInorder + 1, endInorder);return root;}}

测试文件test.cpp

#include"BinaryTree.h"#include<iostream>#include<cstdio>using namespace OrdinaryBinaryTreeSpace;// ====================测试代码====================void Test(char* testName, int* preorder, int* inorder, int length){if (testName != NULL)printf("%s begins:\n", testName);printf("The preorder sequence is: ");for (int i = 0; i < length; ++i)printf("%d ", preorder[i]);printf("\n");printf("The inorder sequence is: ");for (int i = 0; i < length; ++i)printf("%d ", inorder[i]);printf("\n");try{BinaryTreeNode* root = Construct(preorder, inorder, length);PrintTreePre(root);DestoryTree(root);}catch (std::exception& exception){printf("Invalid Input.\n");}printf("\n");}// 普通二叉树//              1//           /     \//          2       3  //         /       / \//        4       5   6//         \         ///          7       8void Test1(){const int length = 8;int preorder[length] = { 1, 2, 4, 7, 3, 5, 6, 8 };int inorder[length] = { 4, 7, 2, 1, 5, 3, 8, 6 };Test("Test1", preorder, inorder, length);}// 所有结点都没有右子结点//            1//           / //          2   //         / //        3 //       ///      4//     ///    5void Test2(){const int length = 5;int preorder[length] = { 1, 2, 3, 4, 5 };int inorder[length] = { 5, 4, 3, 2, 1 };Test("Test2", preorder, inorder, length);}// 所有结点都没有左子结点//            1//             \ //              2   //               \ //                3 //                 \//                  4//                   \//                    5void Test3(){const int length = 5;int preorder[length] = { 1, 2, 3, 4, 5 };int inorder[length] = { 1, 2, 3, 4, 5 };Test("Test3", preorder, inorder, length);}// 树中只有一个结点void Test4(){const int length = 1;int preorder[length] = { 1 };int inorder[length] = { 1 };Test("Test4", preorder, inorder, length);}// 完全二叉树//              1//           /     \//          2       3  //         / \     / \//        4   5   6   7void Test5(){const int length = 7;int preorder[length] = { 1, 2, 4, 5, 3, 6, 7 };int inorder[length] = { 4, 2, 5, 1, 6, 3, 7 };Test("Test5", preorder, inorder, length);}// 输入空指针void Test6(){Test("Test6", NULL, NULL, 0);}// 输入的两个序列不匹配void Test7(){const int length = 7;int preorder[length] = { 1, 2, 4, 5, 3, 6, 7 };int inorder[length] = { 4, 2, 8, 1, 6, 3, 7 };Test("Test7: for unmatched input", preorder, inorder, length);}int main(){Test1();Test2();Test3();Test4();Test5();Test6();Test7();system("pause");return 0;}

运行结果:
Test1 begins:The preorder sequence is: 1 2 4 7 3 5 6 8The inorder sequence is: 4 7 2 1 5 3 8 61  2  4  7  3  5  6  8Test2 begins:The preorder sequence is: 1 2 3 4 5The inorder sequence is: 5 4 3 2 11  2  3  4  5Test3 begins:The preorder sequence is: 1 2 3 4 5The inorder sequence is: 1 2 3 4 51  2  3  4  5Test4 begins:The preorder sequence is: 1The inorder sequence is: 11Test5 begins:The preorder sequence is: 1 2 4 5 3 6 7The inorder sequence is: 4 2 5 1 6 3 71  2  4  5  3  6  7Test6 begins:The preorder sequence is:The inorder sequence is:The tree is emptyTest7: for unmatched input begins:The preorder sequence is: 1 2 4 5 3 6 7The inorder sequence is: 4 2 8 1 6 3 71  2  4  5  3  6  7请按任意键继续. . .



0 0
原创粉丝点击