二叉树的四种遍历

 

前中后递归版:

//二叉树的遍历:前序:中左右   中序:左中右  后序:左右中

void recur(btnode *p){         //递归版
	if(p != nullptr){
		
		cout<<p->date<<endl;  // 前序 count放这
			
		recur(p->left);
			                  //中序cout放在这
		recur(p->right);
			                   //后序cout放在这                    
	}	
}

非递归版:

1. 先中后都是用栈,层用队列

2. 先序用1个栈,先压右再压左,后序两个栈,先压左再压右

3. 层序看问题,一个一个出用1个循环,要求1层1层出用两个循环

a)先序  中左右

先弹栈(访问),再压右孩子(不为空),最后压左孩子(不为空)

压栈顺序是右左,弹栈自然就是左右了!

void preorder(node *head){
	if(head ==nullptr)
		return;
	
	node *cur=head;
	stack<node *> s1;
	s1.push(cur);
	while(s1.size()){
		cur=s1.top();
		s1.pop();
		cout<<cur->val<<endl; // 先序 访问节点
		
		if(cur->right)            // 先压右
			s1.push(cur->right);
		if(cur->left)             // 再压左
			s1.push(cur->left);
	}
	
}

b)中序  左右中

用一个栈去模仿递归的过程,

从根结点开始(cur=root) 不断的做一个while循环, 把cur压栈,然后去它的左结点 cur =cur->left
左子树遍历完之后(也就是cur为空) 弹出一个,给cur 然后去cur的右子树,再遍历它的左子树
那么中序,就是第二次遇到这个结点 也就是弹栈的时候输出。

其实先序也能由这种方法搞定,但是对于n叉树来说,用这种方法实现的先序,很难写!!,所以还是记上面这种方法。

(这种方法的先序,就是第一次遇到这个结点,也就是压栈的时候输出)

void inorder(btnode *head){
	btnode *cur=head;
	stack<btnod *> s1;
	while(cur || !s1.empty()){    //结束的条件是当前为空且栈空
		while(cur){
			
			//cout<<cur->val<<endl;  //前序
			s1.push(cur);
			cur=cur->left;
		}
		
		if(!s1.empty()){
			cur = s1.top();
			s1.pop();
			cout<<cur->data<<endl;  //中序,访问节点
			cur = cur->right;
		}
	}
	//一直往左走,不能走的时候,回栈中取出一个元素,然后去它的右边 ,然后继续往左走
}

c) 后序 左右中

用两个栈,栈1用来实现“中右左“的先序,同时栈1弹出的节点都压入栈2,这样栈2的弹出顺序就是“左右中”。

(记这个,同样为了实现n叉树的后序)

void postorder(btnode *head){
	btnode *cur = head;
	if(cur == nullptr)
		return;
	stack<btnode *> s1,s2;
	s1.push(cur);
	while(!s1.empty()){
		cur=s1.top();
		s1.pop();           // s1 pop的同时压入s2
		s2.push(cur);
		if(cur.left !=nullptr)
			s1.push(cur.left);
		if(cur.right != nullptr){
			s1.push(cur.right);
		}	
	}
	while(!s2.empty()){
		cout<<s2.top()->data<<endl;   //后序访问节点
		s2.pop();
	}
}

另一种实现(but,不推荐记这个,为了n叉树的后序好实现,orz)

后序是左右中,所以轮到中之前,假如中有右孩子,最近一个必定是中的右孩子

后序非递归,本质上是要第3次遇到这个结点的时候输出,哪三次呢?

第一次是进入这个结点

第二次是从它的左子树返回

第三次是从它的右子树返回
后序遍历最重要的就是识别这个返回的动作是谁做的?假如是左子树做的,我们直接去右子树,如果是右子树做的,我们弹栈,并且去更高层。而做到这一点的办法就是当左子树遍历完了,栈顶元素的右孩子是不是等于最近一次弹出并打印的结点。
所以我们每次访问节点的时候,都记录下来(用一个节点指向这个节点),那么下一次要访问节点的时候,就可以知道上一次访问的是它的左孩子节点还是右孩子节点。(访问代表弹出并打印)

具体的办法:
每次左子树遍历完,要访问栈顶元素的时候,把当前设为栈顶元素,然后做个判断
如果当前节点有右孩子且上次访问lastvisited 不等于这个右孩,那么不弹 转向右孩
如果不满足上面的条件,那么弹,且把最近访问的设为当前,当前设为nullptr(这点很重要,为了返回更高层)

d)层序

层序,很简单,用一个队列模拟即可。有两种层序:

1. 先头节点入队 然后进入循环  出队一个 左右分别入队。

2. 两个循环,每次小循环结束之后,之前push进来的节点全部pop干净。

第一种方法,用1个循环,每次把当前pop的节点的各个儿子加进来。

第二种方法,用2个循环,大循环的逻辑是把当前队列的所有节点都pop干净,内部循环的逻辑是加入每个节点的儿子。需要按层输出的层序,即 输出vector<vector<int>> 适合这种方法

1个循环:

void levelorder(btnode *head){
	if(head ==nullptr)
		return;
	queue<btnode *> q;
	btnode * cur;
	q.push(head);
	while(!q.empty()){
		cur = q.front();
		q.pop();
		//队列弹出也不会返回元素 stack不会 orz!!!
		cout<<cur->data<<endl;
		if(cur->left)
			q.push(cur->left);
		if(cur->right)
			q.push(cur->right);
	}
}

2个循环:

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        if(root ==nullptr)
            return {};
        vector<vector<int>> res;
        queue<TreeNode *> q1{{root}};
        TreeNode *cur=nullptr;
        while(q1.size()){
            vector<int> onelevel;
            for(int i=q1.size();i>0;--i){         //小循环要pop干净
                cur=q1.front();
                q1.pop();
                onelevel.push_back(cur->val);
                if(cur->left) q1.push(cur->left);
                if(cur->right) q1.push(cur->right);
            }                                        
            res.push_back(onelevel);
            
        }
        return res;
    }
};
点赞