您的位置:首页 > 其它

根据前序遍历-中序遍历结果 来还原一颗二叉树

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的结构如下

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;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: