遍历二叉树
相同数量的100元和20元钞票落在地上,要想一定时间内捡的钱最多,大家肯定是先捡100元的,因为捡1张100元的等于捡了5张20元的,效率高了很多。捡钱顺序非常重要!
对于二叉树来说,次序同样很重要。 例如,人生会有很多十字路口,就像一个二叉树,初中升高中还是职业学校,高中升大学还是大专,专业选热门还是喜欢的……你选择的次序会直接影响你的人生。其实上面的人生,大多数人都不需要选择,因为心里都直到哪条路更好。
二叉树的遍历是指从根节点出发,按照某种次序依次访问二叉树中的结点,使得每个结点被访问一次且仅被访问一次。
访问是一个抽象操作,例如对每个结点进行打印或者计算等。
1.二叉树的遍历方法
主要分为以下四种:
①前序遍历
若二叉树为空,则返回空;否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。 如下图所示:遍历顺序为ABDGHCEIF。
②中序遍历
若树为空,则返回空;否则从根节点开始,中序遍历根节点的左子树,然后访问根节点,最后中序遍历右子树。 如下图所示:遍历顺序为GDHBAEICF。
③后序遍历
若树为空,则返回空;否则,从左到右先叶子后结点的方式遍历访问左右子树,最后访问根结点。 如下图所示:遍历顺序为GHDBIEFCA。
④层序遍历
若树为空,则返回空,否则,从树的第一层开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。 如下图所示,遍历顺序为:ABCDEFGHI
我们用图形来表示树的结构是非常直观和容易理解的,但是对计算机而言,它只会处理线性序列,而我们刚才提到的四种遍历方法,就是把树的结构变成线性序列,这就方便了计算机处理。
2.前序遍历算法
二叉树的定义是用递归的方式,我们也可以采用递归法来实现前序遍历算法,其C++代码如下:
//树的结点定义
struct TreeNode {
public:
int val;
TreeNode *left;
TreeNode *right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};
//树的前序遍历算法,用vector<int>保存结果
void preOrderTraversal(TreeNode *cur) {
if (cur == nullptr) return;
cout << cur->val;
preOrderTraversal(cur->left);
preOrderTraversal(cur->right);
}
假设我们有如下图这样一棵二叉树,其根节点指针为T,这棵树已经用二叉链表存储在内存中。
我们看看当调用preOrderTraversal(T)函数时,程序是如何运行的:
①调用第一层迭代preOrderTraversal(T),a结点不为nullptr,因此先输出a结点内容,如下:
②接下来调用第二层迭代preOrderTraversal(T->left),即对b结点进行递归,由于b不为nullptr,因此,再输出b结点的内容,如下:
③再调用第三层迭代preOrderTraversal(T->left->left),即对b结点的左孩子进行访问,由于b结点无左孩子,再调用preOrderTraversal(T->left->right),即对结点d进行访问,由于d不为空结点,则打印d结点的内容,如下:
④再调用第四层迭代preOrderTraversal(T->left->right->left),由于d无左孩子,返回第三层,再调用preOrderTraversal(T->left->right->right),由于d也无右孩子,返回第三层层;执行完之后再返回第二层,按同样的法则遍历根节点的右子树,调用第二层迭代preOrderTraversal(T->right)。对右子树的遍历原理同上省略。
3.中序遍历算法
二叉树的中序遍历算法仅仅和前序遍历算法有代码顺序上的差异,其C++代码如下:
void inOrderTraversal(TreeNode *cur) {
if (cur == nullptr) return;
inOrderTraversal(cur->left);
cout << cur->val;
inOrderTraversal(cur->right);
}
对上述同样的二叉树,我们看看当调用inOrderTraversal(T)函数时,程序是如何运行的:
①调用第一层迭代inOrderTraversal(T),a结点不为nullptr,继续调用第二层迭代inOrderTraversal(T->left),b结点也不为nullptr,再继续调用第三层迭代inOrderTraversal(T->left->left)。由于b结点无左子树,因此,返回第二层,打印b的内容,如下:
②接着,调用第三层迭代inOrderTraversal(T->left->right),由于d结点不为nullptr,调用第四层迭代inOrderTraversal(T->left->right->left),由于d无左孩子,返回第三层,接着打印d结点内容,如下:
③接着调用第四层迭代inOrderTraversal(T->left->right->right),由于d也无右孩子,返回第三层。运行结束,在返回上第二层,打印结点a的内容,如下:
④接着调用第二层迭代对根节点a的右子树进行同样的操作,如下,过程省略:
4.后续遍历算法
二叉树的后序遍历算法仅仅和前序和中序遍历算法有代码顺序上的差异,其C++代码如下:
void postOrderTraversal(TreeNode *cur) {
if (cur == nullptr) return;
postOrderTraversal(cur->left);
postOrderTraversal(cur->right);
cout << cur->val;
}
调用过程与上述类似,这里省略。
5.推导遍历结果
如:已知一棵二叉树的前序遍历为ABCDEF,中序遍历为CBAEDF,请问这颗二叉树的后序遍历是多少?
①前序遍历的第一个结点肯定是根结点,那么我们确定了根节点为A。
中序遍历为CBAEDF,则根节点左子树包含结点C、B,根节点右子树包含结点E、D、F,我们可以初步画出二叉树模样,如下:
②前序遍历为A、B、C,则B结点一定是A结点的左孩子,而C就只能是B的孩子,但不确定C是B的左孩子还是右孩子。中序遍历为C、B、A,则C一定为B的左孩子,否则中序遍历会变成B、C、A。进一步确定出二叉树的模样,如下:
③再看根节点的右子树,前序遍历结果为D、E、F,则D一定为A的右孩子,E和F只能是D的子孙,一共有5种情况,再看中序遍历根节点的右子树顺序为E、D、F,E在结点D左侧,F在结点D右侧,只能是E是左孩子,F是右孩子的情况,因此,我们最终得到了二叉树如下:
这样,我们也可以轻松得到后序遍历的结果为:CBEFDA。
下面有2个二叉树遍历的性质:
- 已知前序遍历和中序遍历序列,可以唯一确定一棵二叉树。
- 已知中序遍历和后续遍历序列,可以唯一确定一棵二叉树。
但是,已知前序和后续遍历不能确定一颗二叉树。
如:前序遍历为ABC,后续遍历为CBA。我们可以确定A为根节点,但是不能确定其他顺序。如下二叉树遍历结果都符合上述。