根据前序遍历-中序遍历结果 来还原一颗二叉树
2008-10-19 12:34
393 查看
前序-中序-二叉树还原
对于这样一个二叉树
A
/ /
B C
/ / / /
D E F G
/ / / / / / / /
H I J K M N O P
前序遍历的结果是:ABDHIEJKCFMNGOP,我们称之为PRE
中序遍历的结果是:HDIBJEKAMFNCOGP,我们称之为MID
还原的方法是:
(1)pi指向PRE的第一个字符
(2)用pi从PRE中取一个字符pc
(3)查找pc在MID中出现的位置mi
(4)根据mi确定pc与前一个字符的关系(左孩子/右孩子/没有关系)
(5)pi+1
(6)反复重复(2)~(5)步,直到pi超过了PRE的长度
可以看到,问题的关键在于步骤(4),即如何确定pc与前一个字符的关系。
在这里我们要用到两个辅助结构:
(1)一个链表,存放Helper结构
(2)一个Helper结构,用于记录每一个节点在MID中的下标
链表我们可以用STL的list,Helper的结构如下
当然,二叉树的节点也要有:
好了,我们一步一步来看看如何解决这个还原二叉树的问题
(1) (A, 7)
取PRE第一个字符,然后通过Helper放入list中,并构造出一个list的初始环境
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
PRE: A B D H I E J K C F M N G O P
MID: H D I B J E K A M F N C O G P
【list】
nod 0 A 0
idx -1 7 15
cur ^
prv
cur, prv都是指向list中元素的指针,头尾两个元素表示边界
(2) (B, 3)
取PRE第二个字符,根据list来判定它为谁的左孩子/右孩子
可以看到,pc=B,其在MID中的下标mi为3,简略为(B, 3),以后都这么简略
(B, 3)在(A, 7)左边,因为3 < 7,所以B为A的左孩子,并插入到list中,注意
cur指针的变动,以后每次插入元素后cur指针都会变动。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
PRE: A B D H I E J K C F M N G O P
MID: H D I B J E K A M F N C O G P
【list】
nod 0 B A 0
idx -1 3 7 15
cur ^
prv
(3) (D, 1)
(D, 1)在(B, 3)左边,因为 1 < 3,所以D是B的左孩子,插入list中
【list】
nod 0 D B A 0
idx -1 1 3 7 15
cur ^
prv
(4) (H, 0)
(H, 0)在(D, 1)左边,H是D的左孩子,插入list中
【list】
nod 0 H D B A 0
idx -1 0 1 3 7 15
cur ^
prv
(5) (I, 2)
(I, 2)不在(H, 0)的左边,因为H已经是最左边了,于是我们要prv = cur,cur++
【list】
nod 0 H D B A 0
idx -1 0 1 3 7 15
cur ^
prv ^
(I, 2)不在(H, 0),(D, 1)中间,因为0 < 2 < 1不成立,把prv指向的(H, 0)删除
(6) (I, 2)
(I, 2)不在(D, 1)左边,prv = cur, cur++
【list】
nod 0 D B A 0
idx -1 1 3 7 15
cur ^
prv ^
(I, 2)在(D, 1),(B, 3)中间,因为1 < 2 < 3,所以I是D的右孩子
然后把D删除,把I插入,cur指向I
【list】
nod 0 I B A 0
idx -1 2 3 7 15
cur ^
prv
(7) (E, 5)
(E, 5)不在(I, 2)左边,prv = cur, cur++
【list】
nod 0 I B A 0
idx -1 2 3 7 15
cur ^
prv ^
结果(E, 5)不在(I, 2),(B, 3)中间,把prv指向的(I, 2)删除
(8) (E, 5)
(E, 5)不在(B, 3)左边,prv = cur, cur++
【list】
nod 0 B A 0
idx -1 3 7 15
cur ^
prv ^
(E, 5)在(B, 3),(A, 7)中间,于是E是B的右孩子,删除B,插入E,cur指向E
【list】
nod 0 E A 0
idx -1 5 7 15
cur ^
prv
(9) (J, 4)
(J, 4)是在(E, 5)左边的,于是插入(J, 4),cur指向(J, 4)
【list】
nod 0 J E A 0
idx -1 4 5 7 15
cur ^
prv
(10)(K, 6)
(K, 6)不在(J, 4)左边,prv = cur, cur++
(K, 6)不在(J, 4),(E, 5)中间,因此删除(J, 4)
【list】
nod 0 E A 0
idx -1 5 7 15
cur ^
prv
(11)(K, 6)
(K, 6)不在(E, 5)左边,prv = cur, cur++
(K, 6)在(E, 5),(A, 7)中间,于是K是E的右孩子,删除E,插入K,cur指向K
【list】
nod 0 K A 0
idx -1 6 7 15
cur ^
prv
***到此为止,A的左子树都已经还原完成了,右子树的还原和左子树一样,以下我只给出
***C的确定方法,其余就省略了。
(12)(C, 11)
(C, 11)不在(K, 6)左边,prv = cur, cur++
(C, 11)不在(K, 6),(A, 7)中间,删除(K, 6)
【list】
nod 0 A 0
idx -1 7 15
cur ^
prv
(13)(C, 11)
(C, 11)不在(A, 7)左边,prv = cur, cur++
(C, 11)在(A, 7),(0, 15)中间,于是C是A的右孩子,删除A,插入C,cur指向C
【list】
nod 0 C 0
idx -1 11 15
cur ^
prv
以下省略。。。。
对了,千万不要忘记在确定X节点是Y节点的左/右孩子后要做相应的链接操作哦。
下面给出算法的C++表示,这里我们用iterator来表示cur, prv指针。我们之所以要用list是
因为list在插入/删除元素后iterator不会失效。还有一点,因为list<>::iterator不支持
random access,所以我们要用prv, cur两个iterator表示一前一后,否则的话直接用cur和
cur + 1就行了,这样的话就简单多了。
然后通过验证可以发现,其二叉树还原后的形态的确是我们希望的形态。
#include <iostream>
#include <stack>
#include <string>
#include <list>
using namespace std;
struct TreeNode {
char data;
TreeNode* lChild;
TreeNode* rChild;
public:
TreeNode(char c) : data(c), lChild(0), rChild(0) { }
};
struct Helper {
TreeNode* node;
int index;
public:
Helper(TreeNode* pNode, int idx)
: node(pNode), index(idx) { }
};
int main() {
void preorderTraversal(TreeNode* pTree);
void inorderTraversal(TreeNode* pTree);
void Pre_Mid_Restore(string pre, string mid, TreeNode*& result);
/* A
/ /
B C
/ / / /
D E F G
/ / / / / / / /
H I J K M N O P
*/
string Preorder1 = "ABDHIEJKCFMNGOP";
string Midorder1 = "HDIBJEKAMFNCOGP";
string Preorder2 = "ABDFCEG";
string Midorder2 = "BFDAEGC";
string Preorder3 = "ABDCEFG";
string Midorder3 = "DBAFEGC";
TreeNode* res = 0;
Pre_Mid_Restore(Preorder1, Midorder1, res);
preorderTraversal(res);
inorderTraversal(res);
Pre_Mid_Restore(Preorder2, Midorder2, res);
preorderTraversal(res);
inorderTraversal(res);
Pre_Mid_Restore(Preorder3, Midorder3, res);
preorderTraversal(res);
inorderTraversal(res);
cin.get();
}
//前序-中序-二叉树还原
void Pre_Mid_Restore(string pre, string mid, TreeNode*& result) {
size_t pi = 0; //前序遍历所得字符串的下标
size_t mi = 0; //中序遍历所得字符串的下标
char pc; //前序遍历的字符
result = new TreeNode(pre[pi]); //前序遍历的第一个字符是根节点
TreeNode* pNode = 0;
mi = mid.find(pre[pi]); //在中序字符串中找到前序字符串的当前字符位置
list<Helper> helper;
helper.push_back(Helper(0, -1));
helper.push_back(Helper(result, mi));
helper.push_back(Helper(0, mid.size()));
list<Helper>::iterator cur = helper.begin();
cur++;
/*
下标 -1 7 15
节点 0 A 0
iter ^
*/
for(pi = 1; pi < pre.size(); pi++) {
pc = pre[pi]; //前序字符串的当前字符
mi = mid.find(pc); //在中序字符串中的位置
while(true) {
if (mi < (*cur).index) { //在左边就是左孩子
pNode = new TreeNode(pc);
(*cur).node->lChild = pNode;
cur = helper.insert(cur, Helper(pNode, mi));
break;
}
else { //不在左边
list<Helper>::iterator prv = cur;
cur++;
if((*prv).index < mi && mi < (*cur).index) { //在中间就是右孩子
pNode = new TreeNode(pc);
(*prv).node->rChild = pNode;
helper.erase(prv);
cur = helper.insert(cur, Helper(pNode, mi));
break;
} //不在中间就不是右孩子
else {
helper.erase(prv);
continue;
}
}
//some erase work
}
}
}
//前序遍历
void preorderTraversal(TreeNode* pTree) {
stack<TreeNode*> treeStack;
do {
while(pTree != 0) {
cout << pTree->data;
if(pTree->rChild != 0) {
treeStack.push(pTree->rChild);
}
pTree = pTree->lChild;
}
if(!treeStack.empty()) {
pTree = treeStack.top();
treeStack.pop();
}
}while(!treeStack.empty() || pTree != 0);
cout << endl;
}
//中序遍历
void inorderTraversal(TreeNode* pTree) {
stack<TreeNode*> treeStack;
do {
while(pTree != 0) {
treeStack.push(pTree);
pTree = pTree->lChild;
}
if(!treeStack.empty()) {
pTree = treeStack.top();
treeStack.pop();
cout << pTree->data;
pTree = pTree->rChild;
}
}while(!treeStack.empty() || pTree != 0);
cout << endl;
}
对于这样一个二叉树
A
/ /
B C
/ / / /
D E F G
/ / / / / / / /
H I J K M N O P
前序遍历的结果是:ABDHIEJKCFMNGOP,我们称之为PRE
中序遍历的结果是:HDIBJEKAMFNCOGP,我们称之为MID
还原的方法是:
(1)pi指向PRE的第一个字符
(2)用pi从PRE中取一个字符pc
(3)查找pc在MID中出现的位置mi
(4)根据mi确定pc与前一个字符的关系(左孩子/右孩子/没有关系)
(5)pi+1
(6)反复重复(2)~(5)步,直到pi超过了PRE的长度
可以看到,问题的关键在于步骤(4),即如何确定pc与前一个字符的关系。
在这里我们要用到两个辅助结构:
(1)一个链表,存放Helper结构
(2)一个Helper结构,用于记录每一个节点在MID中的下标
链表我们可以用STL的list,Helper的结构如下
struct Helper { TreeNode* node; int index; public: Helper(TreeNode* pNode, int idx) : node(pNode), index(idx) { } }; |
struct TreeNode { char data; TreeNode* lChild; TreeNode* rChild; public: TreeNode(char c) : data(c), lChild(0), rChild(0) { } }; |
(1) (A, 7)
取PRE第一个字符,然后通过Helper放入list中,并构造出一个list的初始环境
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
PRE: A B D H I E J K C F M N G O P
MID: H D I B J E K A M F N C O G P
【list】
nod 0 A 0
idx -1 7 15
cur ^
prv
cur, prv都是指向list中元素的指针,头尾两个元素表示边界
(2) (B, 3)
取PRE第二个字符,根据list来判定它为谁的左孩子/右孩子
可以看到,pc=B,其在MID中的下标mi为3,简略为(B, 3),以后都这么简略
(B, 3)在(A, 7)左边,因为3 < 7,所以B为A的左孩子,并插入到list中,注意
cur指针的变动,以后每次插入元素后cur指针都会变动。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
PRE: A B D H I E J K C F M N G O P
MID: H D I B J E K A M F N C O G P
【list】
nod 0 B A 0
idx -1 3 7 15
cur ^
prv
(3) (D, 1)
(D, 1)在(B, 3)左边,因为 1 < 3,所以D是B的左孩子,插入list中
【list】
nod 0 D B A 0
idx -1 1 3 7 15
cur ^
prv
(4) (H, 0)
(H, 0)在(D, 1)左边,H是D的左孩子,插入list中
【list】
nod 0 H D B A 0
idx -1 0 1 3 7 15
cur ^
prv
(5) (I, 2)
(I, 2)不在(H, 0)的左边,因为H已经是最左边了,于是我们要prv = cur,cur++
【list】
nod 0 H D B A 0
idx -1 0 1 3 7 15
cur ^
prv ^
(I, 2)不在(H, 0),(D, 1)中间,因为0 < 2 < 1不成立,把prv指向的(H, 0)删除
(6) (I, 2)
(I, 2)不在(D, 1)左边,prv = cur, cur++
【list】
nod 0 D B A 0
idx -1 1 3 7 15
cur ^
prv ^
(I, 2)在(D, 1),(B, 3)中间,因为1 < 2 < 3,所以I是D的右孩子
然后把D删除,把I插入,cur指向I
【list】
nod 0 I B A 0
idx -1 2 3 7 15
cur ^
prv
(7) (E, 5)
(E, 5)不在(I, 2)左边,prv = cur, cur++
【list】
nod 0 I B A 0
idx -1 2 3 7 15
cur ^
prv ^
结果(E, 5)不在(I, 2),(B, 3)中间,把prv指向的(I, 2)删除
(8) (E, 5)
(E, 5)不在(B, 3)左边,prv = cur, cur++
【list】
nod 0 B A 0
idx -1 3 7 15
cur ^
prv ^
(E, 5)在(B, 3),(A, 7)中间,于是E是B的右孩子,删除B,插入E,cur指向E
【list】
nod 0 E A 0
idx -1 5 7 15
cur ^
prv
(9) (J, 4)
(J, 4)是在(E, 5)左边的,于是插入(J, 4),cur指向(J, 4)
【list】
nod 0 J E A 0
idx -1 4 5 7 15
cur ^
prv
(10)(K, 6)
(K, 6)不在(J, 4)左边,prv = cur, cur++
(K, 6)不在(J, 4),(E, 5)中间,因此删除(J, 4)
【list】
nod 0 E A 0
idx -1 5 7 15
cur ^
prv
(11)(K, 6)
(K, 6)不在(E, 5)左边,prv = cur, cur++
(K, 6)在(E, 5),(A, 7)中间,于是K是E的右孩子,删除E,插入K,cur指向K
【list】
nod 0 K A 0
idx -1 6 7 15
cur ^
prv
***到此为止,A的左子树都已经还原完成了,右子树的还原和左子树一样,以下我只给出
***C的确定方法,其余就省略了。
(12)(C, 11)
(C, 11)不在(K, 6)左边,prv = cur, cur++
(C, 11)不在(K, 6),(A, 7)中间,删除(K, 6)
【list】
nod 0 A 0
idx -1 7 15
cur ^
prv
(13)(C, 11)
(C, 11)不在(A, 7)左边,prv = cur, cur++
(C, 11)在(A, 7),(0, 15)中间,于是C是A的右孩子,删除A,插入C,cur指向C
【list】
nod 0 C 0
idx -1 11 15
cur ^
prv
以下省略。。。。
对了,千万不要忘记在确定X节点是Y节点的左/右孩子后要做相应的链接操作哦。
下面给出算法的C++表示,这里我们用iterator来表示cur, prv指针。我们之所以要用list是
因为list在插入/删除元素后iterator不会失效。还有一点,因为list<>::iterator不支持
random access,所以我们要用prv, cur两个iterator表示一前一后,否则的话直接用cur和
cur + 1就行了,这样的话就简单多了。
然后通过验证可以发现,其二叉树还原后的形态的确是我们希望的形态。
#include <iostream>
#include <stack>
#include <string>
#include <list>
using namespace std;
struct TreeNode {
char data;
TreeNode* lChild;
TreeNode* rChild;
public:
TreeNode(char c) : data(c), lChild(0), rChild(0) { }
};
struct Helper {
TreeNode* node;
int index;
public:
Helper(TreeNode* pNode, int idx)
: node(pNode), index(idx) { }
};
int main() {
void preorderTraversal(TreeNode* pTree);
void inorderTraversal(TreeNode* pTree);
void Pre_Mid_Restore(string pre, string mid, TreeNode*& result);
/* A
/ /
B C
/ / / /
D E F G
/ / / / / / / /
H I J K M N O P
*/
string Preorder1 = "ABDHIEJKCFMNGOP";
string Midorder1 = "HDIBJEKAMFNCOGP";
string Preorder2 = "ABDFCEG";
string Midorder2 = "BFDAEGC";
string Preorder3 = "ABDCEFG";
string Midorder3 = "DBAFEGC";
TreeNode* res = 0;
Pre_Mid_Restore(Preorder1, Midorder1, res);
preorderTraversal(res);
inorderTraversal(res);
Pre_Mid_Restore(Preorder2, Midorder2, res);
preorderTraversal(res);
inorderTraversal(res);
Pre_Mid_Restore(Preorder3, Midorder3, res);
preorderTraversal(res);
inorderTraversal(res);
cin.get();
}
//前序-中序-二叉树还原
void Pre_Mid_Restore(string pre, string mid, TreeNode*& result) {
size_t pi = 0; //前序遍历所得字符串的下标
size_t mi = 0; //中序遍历所得字符串的下标
char pc; //前序遍历的字符
result = new TreeNode(pre[pi]); //前序遍历的第一个字符是根节点
TreeNode* pNode = 0;
mi = mid.find(pre[pi]); //在中序字符串中找到前序字符串的当前字符位置
list<Helper> helper;
helper.push_back(Helper(0, -1));
helper.push_back(Helper(result, mi));
helper.push_back(Helper(0, mid.size()));
list<Helper>::iterator cur = helper.begin();
cur++;
/*
下标 -1 7 15
节点 0 A 0
iter ^
*/
for(pi = 1; pi < pre.size(); pi++) {
pc = pre[pi]; //前序字符串的当前字符
mi = mid.find(pc); //在中序字符串中的位置
while(true) {
if (mi < (*cur).index) { //在左边就是左孩子
pNode = new TreeNode(pc);
(*cur).node->lChild = pNode;
cur = helper.insert(cur, Helper(pNode, mi));
break;
}
else { //不在左边
list<Helper>::iterator prv = cur;
cur++;
if((*prv).index < mi && mi < (*cur).index) { //在中间就是右孩子
pNode = new TreeNode(pc);
(*prv).node->rChild = pNode;
helper.erase(prv);
cur = helper.insert(cur, Helper(pNode, mi));
break;
} //不在中间就不是右孩子
else {
helper.erase(prv);
continue;
}
}
//some erase work
}
}
}
//前序遍历
void preorderTraversal(TreeNode* pTree) {
stack<TreeNode*> treeStack;
do {
while(pTree != 0) {
cout << pTree->data;
if(pTree->rChild != 0) {
treeStack.push(pTree->rChild);
}
pTree = pTree->lChild;
}
if(!treeStack.empty()) {
pTree = treeStack.top();
treeStack.pop();
}
}while(!treeStack.empty() || pTree != 0);
cout << endl;
}
//中序遍历
void inorderTraversal(TreeNode* pTree) {
stack<TreeNode*> treeStack;
do {
while(pTree != 0) {
treeStack.push(pTree);
pTree = pTree->lChild;
}
if(!treeStack.empty()) {
pTree = treeStack.top();
treeStack.pop();
cout << pTree->data;
pTree = pTree->rChild;
}
}while(!treeStack.empty() || pTree != 0);
cout << endl;
}
相关文章推荐
- 根据后序-中序遍历结果 来还原一颗二叉树
- 根据二叉树的前序遍历和中序遍历(或者中序遍历和后序遍历)还原二叉树
- 根据层次遍历和中序遍历的结果还原一颗二叉树
- 根据层次遍历和中序遍历的结果还原一颗二叉树
- 根据二叉树的中序遍历和前序遍历,还原二叉树
- 【二叉树】根据二叉树的中序遍历和前序遍历,还原二叉树
- 根据二叉树的先序、中序遍历结果重建二叉树
- 根据前序遍历和中序遍历结果重建二叉树(递归方法)
- 根据二叉树的中序遍历和层次遍历还原二叉树
- 数据结构——根据前序遍历和中序遍历还原二叉树
- 根据二叉树的先序、中序遍历结果重建二叉树
- 根据二叉树的后序遍历以及中序遍历还原二叉树
- 根据前序遍历和中序遍历结果构造二叉树
- 根据前序遍历和中序遍历还原二叉树
- 根据中序遍历结果和前序(后序)遍历结果重构二叉树
- 根据后序遍历和中序遍历结果还原二叉树
- 剑指offer--重建二叉树[根据二叉树的先序、中序遍历结果重建二叉树]
- LeetCode之通过二叉树的中序遍历和前序遍历来还原二叉树
- 根据二叉树的前序遍历和中序遍历结果重建二叉树
- 根据二叉树的前序遍历和中序遍历的结果,重建二叉树