2叉树的遍历-深入理解递归与非递归的本质


学习2叉树, 就要先学习它的遍历, 网上很有很多代码, 递归很好理解, 但非递归对于新手就很难理解了, 其时当你认识到它们的本质的时候, 你就会发现其实非递归遍历也很简单.


定义2叉树的结构

/** *2叉链表示 */ struct btree_t { char data; struct btree_t *lchild; struct btree_t *rchild; }; 先看测试用例 int main() { const char *s = "123##45#67###89#0####"; /*create a tree*/ struct btree_t *root = NULL; create_tree(&root, &s); if(root == NULL) { ERR_RETURN("create_tree error"); } /*leaf num of tree*/ int count = 0; leaf_count(root, &count); printf("\n"); printf("count:%d\n", count); /*depth of a tree*/ int depth = 0; depth_tree(root, &depth); printf("depth:%d\n", depth); /*copy tree and print*/ struct btree_t *croot = NULL; copy_tree(&croot, root); printf("this is copy tree\n"); preorder_tree(croot); printf("\n"); destory_tree(&croot); /** * 遍历, 递归和非递归 */ /*前序*/ printf("\nthis is pretorder_tree tree\n"); preorder_tree(root); printf("\nthis is preorder_tree_nr tree\n"); preorder_tree_nr(root); /*中序*/ printf("\nthis is inorder_tree tree\n"); inorder_tree(root); printf("\nthis is inorder_tree_nr tree\n"); inorder_tree_nr(root); /*后续*/ printf("\nthis is postorder_tree tree\n"); postorder_tree(root); printf("\nthis is postorder_tree_nr tree\n"); postorder_tree_nr(root); printf("\n"); /*destory tree*/ destory_tree(root); return 0; }

首先创建一颗2叉树, 这里使用一个字符串创建

注: (本人很懒, 不喜欢一个字符一个字符往进输, 真心佩服那些一个字符一个字符往进输的人, 在网上这样的人居然有很多, 怪不得外国人都说中国人是勤奋的)

好了, 贴出创建2叉树代码

void create_tree(struct btree_t **root, const char **s) { if (root == NULL || s == NULL) { return; } const char *ps = *s; if (*ps == '\0') { /*没有节点*/ *root = NULL; return; } if (*ps == '#') { /*没有节点*/ *root = NULL; return; } if (*ps != '#') { /*表示有节点*/ struct btree_t *proot = NULL; /*建立root*/ proot = (struct btree_t*) malloc(sizeof(struct btree_t)); if (!proot) { /*error*/ return; } /*为root->data赋值*/ proot->data = *ps; /*建立节点的左子树*/ if (*ps != '\0') { ++ps; } create_tree(&proot->lchild, &ps); *s = ps; /*建立节点右子树*/ if (*ps != '\0') { ++ps; } create_tree(&proot->rchild, &ps); *s = ps; *root = proot; } return; } 这里, 我们就拿先序遍历来说明问题 void preorder_tree(struct btree_t *root) { /*如果是空树*/ if (root == NULL) { return; } printf("%c \t", root->data); preorder_tree(root->lchild); preorder_tree(root->rchild); }

这段代码很容易, 但它值得我们去思考, 为什么会有递归, 递归的在运行时的本质是什么, 你懂得了这个, 那非递归你也就懂了.

1  首先, 当第一次运行它的时候, 如果是NULL时(我喜欢理解为空树),直接返回, 如果不为空, 访问.

2  好,这时到了关键的时候, 在调用自己的时候, 它会把root->lchild压栈, 然后进入函数内部,(第一次调用的时候当然也把root压栈了), 你可以自己把压栈顺序画出来

3  然后, 就是什么时候出栈了, 当传入一颗空树后, 它就会返回到它的上层调用函数, 上层函数再接着往下执行, 直到返回到第一次调用, 这时, 左子树访问完, 往下执行, 访问右子树, 和左子树的访问一样, 最后返回,反问完毕.

3  你因该这时就看出来了, 没错, 递归的执行它借助了栈, 只不过这个栈是操作系统给我们维护的, 我们不需要去手动维护, 所以递归写法很简单, 但新手却很难理解它, 很容易在这里犯迷糊,因为操作系统把这个栈隐藏了, 你没有看到它.

4  这时,你理解了递归的运行之后, 那我们理解非递归就好办了, 我们都知道非递归需要借助栈, 但是为什么借助栈, 这时你因该明白了吧,

其实, 非递归和递归它在运行的时候, 本质上是一样的, 只不非递归是我们自己手动的去维护一个栈而已, 它没有别的东西,

5  所以, 看到网上很多讲递归和非递归去遍历树, 但是却没有指出, 为什么要这样, 可能人家的聪明, 认为这没什么难的, 也就懒的说了, 本人承认自己是个笨蛋, 悟性不高, 所以把最简单, 也是最本质的东西分享出来, 供和我一样笨的人学习, 希望聪明的大牛们就不要来鄙视本人了, 你鄙视一个笨蛋有什么用呢?

废话不多说了, 把 本人写的非递归就贴出来吧, 供大家参考

void preorder_tree_nr(struct btree_t *root) { /*如果是空树, 返回*/ if (root == NULL) { return; } /*创建栈*/ lstack_t *stack = NULL; stack_create(&stack); if (stack == NULL) { printf("stack_create error\n"); return; } struct btree_t *proot = root; /*前序遍历*/ do { if (proot != NULL) { /*访问根*/ printf("%c\t", proot->data); /*压栈*/ stack_push(stack, proot); /*访问左子树*/ proot = proot->lchild; } else if (!stack_empty(stack)) { /*访问右子树*/ stack_pop(stack, (void**) &proot); proot = proot->rchild; } } while (!stack_empty(stack)); return; }
 
   
/*中序*/ void inorder_tree_nr(struct btree_t *root) { if (root == NULL) { /*空树, 不遍历*/ return; } /*create stack*/ lstack_t *stack = NULL; stack_create(&stack); struct btree_t *proot = root; do { /*如果不是空树, 压栈, 遍历左子树*/ if (proot != NULL) { stack_push(stack, proot); proot = proot->lchild; } else if (!stack_empty(stack)) { /** * 如果是空树, 且栈不为空, 出栈, 访问, 遍历右子树 */ stack_pop(stack, (void**) &proot); printf("%c\t", proot->data); proot = proot->rchild; } } while (!stack_empty(stack)); /*销毁栈, 防止内存泄漏*/ stack_destory(stack); return; } 后序遍历稍微麻烦些, 因为你要判断是左子树访问完, 还是右子树访问完, 只有当右子树访问完, 才去访问root /*后序*/ /*后序遍历, 非递归*/ void postorder_tree_nr(struct btree_t *root) { /*如果是空树, 返回*/ if (root == NULL) { return; } /*创建栈*/ lstack_t *stack = NULL; stack_create(&stack); if (stack == NULL) { printf("stack_create() error\n"); return; } struct btree_t *proot = root; /**/ bool finrchild = false; do { if (proot != NULL) { /** * 如果左子树不为空, 压栈 * */ stack_push(stack, proot); proot = proot->lchild; } else if (!stack_empty(stack)) { /** * 如果右子树不为空, 获取栈顶 * 访问右子树 */ stack_top(stack, (void**) &proot); // stack_push(stack, proot); proot = proot->rchild; /*如果右子树是空树, 开始访问root*/ if (proot == NULL) { finrchild = true; while (finrchild) { /*访问root*/ stack_pop(stack, (void**) &proot); printf("%c\t", proot->data); /** * 当栈不为空 * 判断访问完的是左子树, 还是右子树 */ if (!stack_empty(stack)) { struct btree_t *tmp = NULL; stack_top(stack, (void**) &tmp); if (proot == tmp->rchild) { /** * 右子树, 访问完 , 访问root */ finrchild = true; } else { /** * 左子树访问完, 访问右子树 */ proot = tmp->rchild; finrchild = false; } } else { /*访问完成*/ break; } } } } } while (!stack_empty(stack)); /*销毁栈, 防止内存泄漏*/ stack_destory(stack); }

看完这这几段代码, 你会发现, 如果你把它涉及的栈的东西忽略掉, 那它就和递归一样了, 后续的非递归还要把判断左右子树去掉到这里, 你因该对递归和递归有了一个新的认识了, 有兴趣的可以写个树的非递归释放函数来练习一下

注:代码中的栈是自己实现的, 所以在你的机器上因该是不能运行的, 你可以使用你自己的栈



你可能感兴趣的:(数据结构)