二叉树是一种重要的数据结构,在应用和面试中经常出现,这里对二叉树的几种遍历方法进行了总结。
总的来说二叉树的遍历分为深度遍历,广度遍历,方法也有迭代法和递归法。递归法易于理解,但一定要掌握迭代法。

深度遍历

深度遍历主要包括前序遍历、中序遍历和后序遍历,前中后都是指的节点的访问次序。

数据结构多叉树java实现 多叉树递归遍历_数据结构多叉树java实现

1.递归方法

写递归主要是把握住以下三个步骤:
1.确定递归函数的参数和返回值:确定那些参数需要“递”写入形参中,根据“归”的数据确定返回值类型。
2.确定终止条件:这是非常重要的一步,如果终止条件没写对,递归没有终止,系统的内存栈必然溢出。
3.确定单层递归操作:确定相应的逻辑操作,当然里面肯定要包括对自己的调用!

前序遍历

1.确定递归函数的参数和返回值
需要传入节点的指针和保存遍历结果的数组,同时注意为了不使用返回数组传递,这里需要对数组采用引用的方式。
void pretrval(Treenode* cur,vector<int> & vec) 2.确定终止条件
当遇到空节点时,可认为终止,无需继续向下遍历。

if(node==nullptr)
{
	return;//这里返回值类型为void,所以可以只写return
}

3.确定单层递归操作
单层的操作就是,将此节点的数值存入vec数组中,然后继续对左子树和右子树进行遍历。

vec.push_back(cur->val);//中
pretrval(cur->left);//左  这里不需要判断cur->val是否为空,因为执行此语句后带入后,终止条件会判断
pretrval(cur->right);//右

将三者结合起来,就是完整的代码了

class Solution {
public:
    void pretravl(TreeNode* cur, vector<int>& vec) {
        if (cur == NULL) return;
        vec.push_back(cur->val);    // 中
        pretravl(cur->left, vec);  // 左
        pretravl(cur->right, vec); // 右
    }
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> result;
        pretravl(root, result);
        return result;
    }
};

通过递归过程大家也能明白为什么叫深度搜索了吧,它会一直优先递归左子树,直到左子树为空(达到左子树最深处),才会”归“回上一步,继续递归右子树。

中序遍历

递归法一个很大的优点是便于理解学习,只要稍作改动就可以根据一种遍历次序,写出其他两种遍历算法。

class Solution {
public:
    void midtravl(TreeNode* cur, vector<int>& vec) {
        if (cur == NULL) return;
        midtravl(cur->left, vec);  // 左
        vec.push_back(cur->val);    // 中
        midtravl(cur->right, vec); // 右
    }
    vector<int> midorderTraversal(TreeNode* root) {
        vector<int> result;
        midtravl(root, result);
        return result;
    }
};

后序遍历

class Solution {
public:
    void lsttravl(TreeNode* cur, vector<int>& vec) {
        if (cur == NULL) return;
        lsttravl(cur->left, vec);  // 左      
        lsttravl(cur->right, vec); // 右
        vec.push_back(cur->val);    // 中
    }
    vector<int> lstorderTraversal(TreeNode* root) {
        vector<int> result;
        lsttravl(root, result);
        return result;
    }
};

2.迭代法

递归的底层实现就是栈,所以,是否可以试着用递归和栈实现以上的遍历呢???
栈是(FILO)型的数据结构,所以要仔细考虑入栈顺序。

前序遍历

class Solution
{
public:
	vector<int> preorderTraversal(TreeNode* root)
	{
		stack<TreeNode*>st;
		if(root)
		{
			st.push(root);
		}
		vector<int>result;
		while(!st.empty())
		{
			TreeNode* node=st.top();
			st.pop();
			result.push_back(node->val);
			if(node->right)
			{
				st.push(node->right);
			}
			if(node->left)
			{
				st.push(node->left);
			}
		}
		return result;
	}
};

上面压入栈中的确保了不为空指针,因为在加入左右孩子节点时都先进行了判断;当然还可以换另一种写法,在内部判断,不为空遍历此节点,为空时就pop()掉,继续下一次遍历。

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*> st;
        vector<int> result;
        st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();                      // 中
            st.pop();
            if (node != NULL) 
            {
            	result.push_back(node->val);
            }
            else 
            {
            	continue;//continue不在执行加入节点操作,总会把之前加入的多个nullptr全部pop()掉,使st为空结束循环。
            }
            st.push(node->right);                           // 右
            st.push(node->left);                            // 左
        }
        return result;
    }
};

后序遍历
后序遍历如果直接用栈实现还是比较难处理的,这是因为访问顺序和处理顺序不一致。后序遍历时,会先访问节点(中),然后根据节点访问左右孩子(左右/右左),但使用栈时无法实现节点(中)在最后出栈。

细心的同学应该可以发现,使用栈只能实现”中“先出栈的情况,所以也可以实现中右左的出栈顺序,然后对结果反转就可以实现后序遍历左右中

变换如下:
前序遍历(中左右)—>中右左—>左右中

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*> st;
        vector<int> result;
        st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();                      // 中
            st.pop();
            if (node != NULL) result.push_back(node->val);
            else continue;
            st.push(node->left);                            // 左
            st.push(node->right);                           // 右
        }
        reverse(result.begin(), result.end()); // 将结果反转之后就是左右中的顺序了
        return result;
    }
};

中序遍历
中序遍历放在最后是因为,它无法通过前序遍历稍作修改实现。
迭代过程主要有两个操作:
1.处理:将元素放进result数组中
2.访问:遍历节点

因为前序遍历的顺序是中左右,先访问的元素是中间节点,要处理的元素也是中间节点,所以刚刚才能写出相对简洁的代码,因为要访问的元素和要处理的元素顺序是一致的,都是中间节点

但中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点(也就是在把节点的数值放进result数组中),这就造成了处理顺序和访问顺序是不一致的

小结:用栈(先处理后访问)的方式只能实现前序遍历(中左右)和伪前序遍历(中右左),后序遍历之所以能够实现的原因是在可以由伪前序遍历反转得到。

使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。

中序遍历的迭代法容易忘!多学习

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        TreeNode* cur = root;
        while (cur != NULL || !st.empty()) {
            if (cur != NULL) { // 指针来访问节点,访问到最底层
                st.push(cur); // 讲访问的节点放进栈
                cur = cur->left;                // 左
            } else {
                cur = st.top(); // 从栈里弹出的数据,就是要处理的数据(放进result数组里的数据)
                st.pop();
                result.push_back(cur->val);     // 中
                cur = cur->right;               // 右
            }
        }
        return result;
    }
};

3.统一的迭代法

上面迭代法其实是分成了两类,这里可以使用nullptr标记法实现稍作统一的迭代法,只需要稍微修改就可以任意更改为前、中、后序遍历。
前序遍历

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        if(root) st.push(root);//先访问的中
        while (!st.empty()) 
        {
        	TreeNode* cur=st.top();
        	st.pop();//需要弹出中,访问过,但弹出未处理,一会再加入时需要标记
        	if(cur!=nullptr)
        	{
	            if (cur->right)
	            { 
	                st.push(cur->right); 
	            } 
	            if (cur->left)
	            { 
	                st.push(cur->left); 
	            } 
	           	st.push(cur);
	            st.push(nullptr);//前序遍历需要中节点最先出栈,所以弹出后加入左右孩子后在重新入栈
			}
			else 
			{
				cur=st.top();
                st.pop(); // 把标记nullptr弹出后,在从栈里弹出的数据,就是要处理的数据(放进result数组里的数据)
                result.push_back(cur->val);     // 中
            }
        }
        return result;
    }
};

中序遍历

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        if(root) st.push(root);//先访问的中
        while (!st.empty()) 
        {
        	TreeNode* cur=st.top();
        	st.pop();//需要弹出中,访问过,但弹出未处理,一会再加入时需要标记
        	if(cur!=nullptr)
        	{
	            if (cur->right)
	            { 
	                st.push(cur->right); 
	            } 
	            st.push(cur);
	            st.push(nullptr);
	            if (cur->left)
	            { 
	                st.push(cur->left); 
	            } 
			}
			else 
			{
				cur=st.top();
                st.pop(); // 把标记nullptr弹出后,在从栈里弹出的数据,就是要处理的数据(放进result数组里的数据)
                result.push_back(cur->val);     // 中
            }
        }
        return result;
    }
};

后序遍历

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        if(root) st.push(root);//先访问的中
        while (!st.empty()) 
        {
        	TreeNode* cur=st.top();
        	st.pop();//需要弹出中,访问过,但弹出未处理,一会再加入时需要标记
        	if(cur!=nullptr)
        	{
        		st.push(cur);
	            st.push(nullptr);
	            if (cur->right)
	            { 
	                st.push(cur->right); 
	            } 
	            if (cur->left)
	            { 
	                st.push(cur->left); 
	            } 
			}
			else 
			{
				cur=st.top();
                st.pop(); // 把标记nullptr弹出后,在从栈里弹出的数据,就是要处理的数据(放进result数组里的数据)
                result.push_back(cur->val);     // 中
            }
        }
        return result;
    }
};

上面三种方法是不是有一种和谐统一之美啊!

广度遍历

其实就是层序遍历,逐层的遍历得到节点值。

数据结构多叉树java实现 多叉树递归遍历_数据结构多叉树java实现_02


层序遍历需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑,而是用栈先进后出适合模拟深度优先遍历也就是递归的逻辑

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        queue<TreeNode*> que;
        if (root != NULL) que.push(root);
        vector<vector<int>> result;
        while (!que.empty()) {
            int size = que.size();
            vector<int> vec;
            // 这里一定要使用固定大小size,不要使用que.size(),因为que.size是不断变化的
            for (int i = 0; i < size; i++) {
                TreeNode* node = que.front();
                que.pop();
                vec.push_back(node->val);
                if (node->left) que.push(node->left);
                if (node->right) que.push(node->right);
            }
            result.push_back(vec);
        }
        return result;
    }
};

N叉树的遍历

会了二叉树,N叉树自然就会了,不过是在添加节点时不只有左右孩子,还可能有三个或多个孩子,只需要判断孩子的个数,添加一个循环即可。
可以用leetcodde429练习!