昨个笔试让写二叉树中序的非递归,写的不好,回去仔细研究了一下,发现还是没对二叉树的遍历深刻的理解。
非递归的遍历其实也是按着遍历的定义来的,只要好好理解,就会很容易写出来,其实写出来不难,难在如何把它写好。
非递归需要用棧来存储节点。
struct btree {
char data;
struct btree * left;
struct btree * right;
};
//先序递归
void PreOrder(struct btree* b){
if(b != NULL){
cout << b -> data;
PreOrder(b -> left);
PreOrder(b -> right);
}
}
//非递归
void PreOrder_(struct btree * b){
stack<struct btree *> sta;
while(b || !sta.empty()){
while(b){ //一直访问左子树,并输出
cout << b -> data;
sta.push(b);
b = b -> left;
}
b = sta.top(); //左子树为空,就把棧顶弹出,并去访问棧顶元素的右子树
sta.pop();
b = b -> right;
}
}
非递归抽象下,其实也就是访问根节点,然后访问左子树,并入棧,左子树空就去访问右子树。
//中序
void Inorder(struct btree *b){
if(b != NULL){
Inorder(b -> left);
cout << b -> data;
Inorder(b -> right);
}
}
//非递归
void Inorder_(struct btree *b){
stack<struct btree*> sta;
while(b || !sta.empty()){
while(b){ //访问左节点并入棧
sta.push(b);
b = b -> left;
}
b = sta.top(); //左子树为空则访问根节点,之后在访问右子树
cout << b -> data;
sta.pop(); //这里每次pop之后去访问右子树,这样就保证一个节点只入棧一次
b = b -> right;
}
}
后序的非递归我想了很久,我一直再考虑是否可以不借助变量记录访问过的节点,来遍历,但是发现不可行,因为访问顺序就是左右中,左右没访问之前,中是不能出棧的。之后就是考虑这个变量如何设置,最开始想到的是用set来记录访问过的节点,这个方法肯定是要被pass的,要是1w节点存储空间太大,后来看到有人用数组做的,比set好很多了,可是代码实现写的不够好,后来想了一种简单的判别方式:
类似中序的非递归,后序是左右中,也就是说每次从棧顶取出元素的时候,说明这个节点的左子树肯定遍历完了,之后只需要去判断它的右子树,重点就是判断右子树是否被入棧过,如果没有入棧过,就如中序一样访问右子树,如果右子树访问过了,那么这个右节点一定是上一次被输出的(或者说最近一次被输出)节点,因为访问顺序是左右中,访问完右子树就该根节点了,所以只需要一个指针记录最近被输出的节点,与当前要访问节点的右节点做对比,相同就直接输出当前节点,不同就去访问右子树。
说的有点罗嗦,看代码结合理解。
//后序
void PostOrder(struct btree *b){
if(b != NULL){
PostOrder(b -> left);
PostOrder(b -> right);
cout << b -> data;
}
}
//非递归
void PostOrder_(struct btree* b){
stack<struct btree *> sta;
struct btree * p = NULL;
while(b || !sta.empty()){
while(b){
sta.push(b);
b = b -> left;
}
b = sta.top();
if(b -> right == NULL || b -> right == p){
cout << b -> data;
p = b; //记录被访问的节点
sta.pop();
b = NULL; //置为NULL,避免上边的while循环
}else{
b = b -> right;
}
}
}