1.问题定义
对于二叉树的3中遍历方法,相信大家耳熟能详。那么如果我们知道了其中的两种遍历结果,能不能把一棵二叉树重新建立起来?给定一棵二叉树,假设每个节点都用唯一的字符来表示,没有重复。结构如下:
struct _Node { _Node* Lchild; _Node* Rchild; char data; };
假设已有了前序遍历和中序遍历的结果,希望通过一个算法来重建这棵树。
前序遍历:a, b, d, c, e, f 显然a是根节点
中序遍历:d, b, a, e, c, f a前面的d, b是a的左子树中的节点,a后面的e, c, f是a右子树中的节点
重建后的图如下:
2.分析与解法
不难发现a是前序遍历中的第一个节点,把中序遍历分成前后两个部分,分别是a的左右子树中的节点,长度分别为2,3,前序遍历中的第二个节点b又把d, b分成了前后两部分(后一部分没有节点),分别是b的左右子树中的节点,长度分别为1,0……这样递归下去,能找出每一个节点的左右子树中的节点,当节点没有左右子树的时候即为叶节点,当一个节点它的左右子树长度为1时,此节点的左右子树就一个节点,为0时,表示此节点没有左子树或右子树。编程之美上的算法如下:
#include <iostream> using namespace std; #define TREELEN 6 struct _Node { _Node* Lchild; _Node* Rchild; char data; }; void ReBulid(char* pPreOrder,//当前节点的前序遍历结果 char* pInOrder, //当前节点的中序遍历结果 int nTreeLen, //当前节点为根的树长度 _Node**pRoot) //当前根节点 { if (pPreOrder == NULL || pInOrder == NULL)//边界检查 { return; } _Node* pTemp = new _Node; pTemp->data = *pPreOrder; pTemp->Lchild = NULL; pTemp->Rchild = NULL; if (*pRoot == NULL)//如果当前根节点为空,把前序遍历的第一个节点赋给它 { *pRoot = pTemp; } if (nTreeLen == 1)//如果当前树长度为1,表明此节点是最后一个节点 { return; } //寻找子树长度 char* pOrgInOrder = pInOrder; char* pLeftEnd = pInOrder; int nTempLen = 0; //找到左子树的结尾 while(*pPreOrder != *pLeftEnd) { if (pPreOrder == NULL || pLeftEnd == NULL) { return; } nTempLen++; if (nTempLen > nTreeLen) { break; } pLeftEnd++; } //寻找左子树长度 int nLeftLen = 0; nLeftLen = (int)(pLeftEnd - pOrgInOrder); //寻找右子树长度 int nRightLen = 0; nRightLen = nTreeLen - nLeftLen - 1; //重建左子树 if (nLeftLen > 0) { ReBulid(pPreOrder + 1, pInOrder, nLeftLen, &((*pRoot)->Lchild)); } //重建右子树 if (nRightLen > 0) { ReBulid(pPreOrder + nLeftLen + 1, pInOrder + nLeftLen + 1, nRightLen, &((*pRoot)->Rchild)); } }; void main() { char PerO[6] = {'a', 'b', 'd', 'c', 'e', 'f'}; char InO[6] = {'d', 'b', 'a', 'e', 'c', 'f'}; _Node * pRoot = NULL; ReBulid(PerO, InO, TREELEN, &pRoot); }
这里可能引起内存泄露。_Node * pRoot = NULL;并没有释放空间,详见:http://blog.youkuaiyun.com/flyinghearts/archive/2010/05/31/5638123.aspx
另这里给几个内存思考问题(有点肤浅,见笑):
void GetMemory(char *p) { p = (char *)malloc(100); } void Test(void) { char *str = NULL; GetMemory(str); strcpy(str, "hello world"); printf(str); } 请问运行Test 函数会有什么样的结果? 答:程序崩溃。 因为 GetMemory 并不能传递动态内存,Test 函数中的 str 一直都是 NULL。 strcpy(str, "hello world");将使程序崩溃
char *GetMemory(void) { char p[] = "hello world"; return p; } void Test(void) { char *str = NULL; str = GetMemory(); printf(str); } 请问运行Test 函数会有什么样的结果? 答:可能是乱码。 因为 GetMemory 返回的是指向“栈内存”的指针,该指针的地址不是 NULL,但其原 现的内容已经被清除,新内容不可知。
void GetMemory2(char **p, int num) { *p = (char *)malloc(num); } void Test(void) { char *str = NULL; GetMemory(&str, 100); strcpy(str, "hello"); printf(str); } 请问运行Test 函数会有什么样的结果? 答: (1)能够输出hello (2)内存泄漏
void Test(void) { char *str = (char *) malloc(100); strcpy(str, “hello”); free(str); if(str != NULL) { strcpy(str, “world”); printf(str); } } 请问运行Test 函数会有什么样的结果? 答:篡改动态内存区的内容,后果难以预 料,非常危险。因为 free(str);之后,str 成为野指针,if(str != NULL)语句不起作用。
//关于重建二叉树的方法,还有很多。我觉得有一种方法也可行:假如前序遍历的为a,b,c,那么中序遍历的结果只有5种:c,b,a | a,b,c | b,c,a | c,b,a | b,a,c。
当然前提是前序遍历的头两个节点,即a,b不能有共同的父亲。用a,b,c找出中序遍历中a,b,c分别的位置,就对应出a,b,c的树型结构。
还有一点,a是根节点,这没问题,一定要用a先找完a的左子树,然后再用a找完a的右子树,如前序:a,b,d,e,x,g,c,k,h,z , 中序为:d,b,x,g,e, (a) ,k,c,h,z
要这样操作:先a,b,d,e,x,g ->(a,b,d) ->(b,d,e)->(d,e,x)->(e,x,g)->(x,g)。然后a,c,k,h,z->(a,c,k)->(,c,k,h)->(k,h,z)->(h,z)。
*还要注意:(a,b,d)中前两个节点a,b不能有共同的父亲,否则这步操作不作!那么最后只有两个节点怎么办呢?
如:(x,g),如果在中序遍历中g在a的前面则说明g是a的右孩子,即中序中x,g反序了。否则g为a的左孩子。