二叉树的DFS迭代遍历

二叉树的递归遍历仍是很简单的,代码也很好写。可是在有的时候可能会要求用迭代法来遍历二叉树,或者你两种方法都会的话确定也更好。既然能够用递归实现,而递归的实质其实就是栈,那么迭代法遍历很显然就是会用到栈。可是入栈和出栈的顺序是须要考虑清楚的,感受本身常常会忘记怎么处理这个顺序,从而致使代码又不会写了,那么今天就来总结一个这个入栈出栈的顺序,逻辑理顺了,代码就不难写了。数据结构

1. 前序遍历的迭代实现

前序遍历的遍历顺序要保证:中->左->右。 其递归实现以下:函数

void preOrder(TreeNode* p){
    if(p == NULL)
        return;
    cout << p->val <<endl;
    preOrder(p->left);
    preOrder(p->right);
}
复制代码
1
   /   \
  2     3
 / \   / \
4   5 6   7
复制代码

考虑到如上一个简单的二叉树,那么用先序遍历的实现结果应该是1 2 4 5 3 6 7。
一开始很显然从根节点开始沿着左子树往下遍历,这部分入栈也很简单,就是一直把当前节点入栈,在把当前节点指向左子树,直到当前节点为NULL。对应在这个例子里面就是入栈了1 2 4,而后当前节点已经指向了4的左子树,它是NULL。
问题是碰到这种状况下一步应该怎么作才能保证按正确的顺序遍历到2的右节点5呢?
首先很明确,要想使当前节点直到节点5,那么就必须经过指向2的右树来得到。如今栈顶是4,那么显然4应该被弹出来。
那么4又该何时被弹出来呢?
应该是遍历完了4的左树,且得到了4的右树指针以后,遍历4的右树以前。这主要是由于前序遍历要先遍历完左子树才能遍历右子树,在遍历右子树的时候,获取其右子树的指针前,不能把4删掉。而一旦左子树遍历完了以后,得到了指针立刻要遍历右子树以前,应该当即把4出栈,由于遍历其右子树,假设其右子树不为空的话,那么就还会有节点入栈,所以得在遍历其右子树以前对4出栈,否则后面就没法在合适的节点出栈了。
所以,这个出栈顺序就肯定下来了:当前节点的左子树已经遍历完,得到了其右子树的指针以后,且遍历右子树以前,应该把栈顶元素弹出。学习

void preOrder(TreeNode* p){
    if( p == NULL)
        return;
    stack<TreeNode*> s;
    while(p != NULL || !s.empty()){
        while(p){
            s.push(p);
            cout<< p->val <<endl;
            p = p->left;
        }
        p = s.top()->right;
        s.pop();
    }
}
复制代码

2. 中序遍历的迭代实现

中序遍历的顺序:左->中->右。 中序遍历的栈的维护包括入栈出栈操做和前序遍历一致,不一样的是,前序遍历按入栈的顺序输出节点的值,中序遍历弹出的时候输出节点的值。这主要是由于当前节点得等其左子树所有遍历完了以后右子树开始遍历以前才能输出,从前面的分析,那就正好是当前节点弹出的时间点。
代码以下:ui

void inOrder(TreeNode* p){
    if( p == NULL)
        return;
    stack<TreeNode*> s;
    while(p != NULL || !s.empty()){
        while(p){
            s.push(p);
            p = p->left;
        }
        p = s.top()->right;
        cout << s.top()->val << endl;
        s.pop();
    }
}
复制代码

3. 后序遍历的迭代实现

后序遍历的顺序:左->右->中。
后序遍历和前序、中序不太同样,主要是由于每一个节点都会前后用到三次。得到左节点须要一次,得到右节点须要一次,最后还须要在输出本身。而前序和中序输出自身和获取右节点是连续的,所以只须要两次。由于只须要两次,全部经过一次入栈和一次出栈就能够保证顺序正确。可是后序是不够的,因此还须要一个数据结构来存储信息,代表是第二次到仍是第三次到当前节点。spa

void afterOrder(TreeNode* root) {
	if (root== NULL)
		return;
	stack<TreeNode*> s;
	vector<TreeNode*> v;
	s.push(root);
	TreeNode* p = NULL;
	while (!s.empty()) {
		p = s.top();
		bool flag1 = false;
		bool flag2 = false;

		if (p->left == NULL)
			flag1 = true;
		if (p->right == NULL)
			flag2 = true;
		if (!flag1) {
			auto it = v.begin();
			for (; it != v.end(); it++) {
				if (*it == p->left)
					break;
			}
			if (it != v.end()) {
				flag1 = true;
			}
		}
		if (!flag2) {
			auto it = v.begin();
			for (; it != v.end(); it++) {
				if (*it == p->right)
					break;
			}
			if (it != v.end()) {
				flag2 = true;
			}
		}
		if (flag1 == true && flag2 == true) {
			v.push_back(s.top());
			s.pop();
		}
		else if (flag1 == false) {
			s.push(p->left);
		}
		else if (flag1 == true && flag2 == false) {
			s.push(p->right);
		}		
	}
	for (auto p : v) {
		cout << p->val << endl;
	}
}
复制代码

4. 总结

其实迭代法遍历二叉树不止以上介绍的方法,可是我以为这种方法仍是很好理解的,由于这种遍历的顺序是和递归函数调用的顺序很像的。若是感兴趣的话,也可学习其余迭代方法,进行对比。指针

相关文章
相关标签/搜索