二叉树的4种遍历-递归+迭代


目录
  • 二叉树的前序遍历
    • 递归写法
    • 迭代写法
  • 二叉树的中序遍历
    • 递归写法
    • 迭代写法
  • 二叉树的后序遍历
    • 递归写法
    • 迭代写法
    • 转换写法
  • 莫里斯Morris遍历
  • 层次遍历
    • 迭代写法
    • 递归写法

二叉树的前序遍历

前序遍历:root left-child right-child

题源:Leetcode 144

递归写法

//Preorder
class Solution {
public:
    vector preorderTraversal(TreeNode* root) {//参数为二叉树的根结点
        //如果结点非空
        if(root){
            ret.push_back(root->val);//访问根结点
            preorderTraversal(root->left);//遍历左子树
            preorderTraversal(root->right);//遍历右子树
        }
        return ret;
    }
private:
    vector ret;//返回值  
};

迭代写法

首先,初步了解一下迭代是什么概念:

迭代是重复反馈过程的活动,其目的通常是为了逼近所需目标或结果。每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值。

理解例子:

迭代法是用于求方程或方程组近似根的一种常用的算法设计方法。设方程为f(x)=0,用某种数学方法导出等价的形式x=g(x),然后按以下步骤执行:

⑴ 选一个方程的近似根,赋给变量x0;

⑵ 将x0的值保存于变量x1,然后计算g(x1),并将结果存于变量x0;

⑶ 当x0与x1的差的绝对值还大于指定的精度要求时,重复步骤⑵的计算[1]

对题目的迭代分析:

递归本质上和栈上差不多,本题的迭代方式实际上就是在模拟递归的过程。使用stack来模拟系统栈

stack来存放结点指针,出栈表示访问数据,非空则入栈。先序序列中先左后右访问,所以入栈时先右后左

class Solution {
public:
	vector preorderTraversal(TreeNode* root) {
		stack s;//头文件
        //先判断根结点是否为空
		if (root == NULL)
			return ret;
		s.push(root);//root非空
        
		while (!s.empty()) {//迭代结束(成功)条件
			TreeNode* get = s.top();
			//出栈模拟访问该结点
			ret.push_back(get->val);
			s.pop();

			//再判断左右子树的压栈
			if (get->right)
				s.push(get->right);
			if (get->left)
				s.push(get->left);
		}
		return ret;
	}
private:
	vector ret;//返回值
};

前两种时间空间复杂度均为O(n),不过使用递归比较容易有堆栈溢出的风险

二叉树的中序遍历

前序遍历:left-child root right-child

题源:Leetcode 94

递归写法

class Solution {
public:
    vector inorderTraversal(TreeNode* root) {
        if(root){
            inorderTraversal(root->left);
            ret.push_back(root->val);
            inorderTraversal(root->right);
        }
        return ret;
    }
private:
    vector ret;
};

迭代写法

基于栈的遍历算法

class Solution {
public:
	vector inorderTraversal(TreeNode* root) {
		stack s;
		TreeNode* p = root;		
		while (p || !s.empty()) {
            //利用第二个循环来进行入栈操作
			while (p) {
				s.push(p);
				p = p->left;//一直压入左子树
			}
			p = s.top();
			ret.push_back(p->val);//访问
			s.pop();
			p = p->right;//遍历右子树
		}
		return ret;
	}
private:
	vector ret;//返回
};

二叉树的后序遍历

前序遍历:left-child right-child root

题源:Leetcode 145

递归写法

class Solution {
public:
    vector postorderTraversal(TreeNode* root) {
        if(root){
            postorderTraversal(root->left);
            postorderTraversal(root->right);
            ret.push_back(root->val);
        }
        return ret;
    }
private:
    vector ret;
};

迭代写法

二叉树的后序遍历迭代算法会比较复杂,一个结点的出栈与否跟其是否有右子树有关。

class Solution {
public:
	vector postorderTraversal(TreeNode* root) {
		stack s;
		TreeNode* p = root;
		TreeNode* last = NULL;//记录前一个访问的结点
		while (p || !s.empty()) {
			while (p) {
				s.push(p);
				p = p->left;
			}
			
			p = s.top();
            //若右节点已经访问过或者没有右节点 则输出该节点值
			if (!p->right || p->right == last) {
				s.pop();
				ret.push_back(p->val);
				last = p;
				p = NULL;
			}
			else {
				p = p->right;
				last = NULL;//可有可无
			}
		}
		return ret;
	}
private:
	vector ret;
};

转换写法

前序遍历的非递归比后序遍历更容易理解一些

前序遍历:根 左 右

后序遍历:左 右 跟

改写前序遍历为:根 右 左 再进行逆序,即为后序遍历

class Solution {
public:
	vector preorderTraversal(TreeNode* root) {
		stack s;//头文件
        //先判断根结点是否为空
		if (root == NULL)
			return ret;
		s.push(root);//root非空
        
		while (!s.empty()) {//迭代结束(成功)条件
			TreeNode* get = s.top();
			//出栈模拟访问该结点
			ret.push_back(get->val);
			s.pop();

			//不同于先序,先压左
			if (get->left)
				s.push(get->right);
			if (get->right)
				s.push(get->left);
		}
        ret.reverse(ret.size());//vector的逆序需要额外开销
		return ret;
	}
private:
	vector ret;//返回值
};

vector的逆序开销比较大,可以选择其他的实现形式比如链表等。

莫里斯Morris遍历

其实是一种动态的二叉树线索化,省去了栈的空间,空间复杂度为O(1),但实际应用并不广,先埋个坑在这里。

层次遍历

层次遍历就是逐层遍历树的结构,相当于广度优先搜索,通常使用队列来做广度优先搜索。

题源:Leetcode 102

本题的主要关键是还要梳理出层次来,相较于以前写过的层次遍历输出

//直接层次遍历输出,无梳理层次,非本题题解
void function(TreeNode* root){
	TreeNode* p=root;
    queue Q;
    if(p)
        Q.push(p);
    
    while(!Q.empty()){
        p=Q.front();
        cout<data<left)
            Q.push(Q->left);
        if(Q->right)
            Q.push(Q->right);
	}
}

而本题要求按照层次梳理,需要了解每一层的结点个数

迭代写法

广度遍历的思想,时间空间复杂度均为O(n)

class Solution {
public:
	vector> levelOrder(TreeNode* root) {
		vector> ret;//返回
		vector p;//临时一维vector
		queue Q;//队列
		TreeNode* T = root;
		
		if (T == NULL)
			return ret;

		Q.push(T);
		while (!Q.empty()) {
			int size = Q.size();//本层次的结点数目
			p.clear();//重置临时vector
            //固定数目的同层次结点pop
			for (int i = 0; i < size; i++) {
				T = Q.front();
				p.push_back(T->val);
				Q.pop();
				if (T->left)
					Q.push(T->left);
				if (T->right)
					Q.push(T->right);
			}
			ret.push_back(p);//一次循环结束,意味着一次层次遍历结束
		}		
		return ret;
	}
};

递归写法

先埋个坑


  1. https://baike.baidu.com/item/迭代/8415523?fr=aladdin 百度百科-迭代 ??