引言
二叉树是一种非常重要的数据结构,很多其它数据结构都是基于二叉树的基础演变而来的。对于二叉树,有深度遍历和广度遍历,深度遍历有前序、中序以及后序三种遍历方法,广度遍历即我们平常所说的层次遍历。因为树的定义本身就是递归定义,因此采用递归的方法去实现树的三种遍历不仅容易理解而且代码很简洁,而对于广度遍历来说,需要其他数据结构的支撑,如堆。
这里我们主要介绍二叉树的三种遍历方式:前序、中序、后序,其中序遍历最为重要。
遍历的主要思想:
前序遍历:根结点 —> 左子树 —> 右子树
中序遍历:左子树—> 根结点 —> 右子树
后序遍历:左子树 —> 右子树 —> 根结点
用下面的图举个例子:
A:根节点、B:左节点、C:右节点,前序顺序是ABC;中序顺序是BAC;后序顺序是BCA
再看一个比较复杂的树:
前序遍历:ABCDEFGHK;中序遍历:BDCAEHGKF;后序遍历:DCBHKGFEA
递归方法实现前中后序遍历
我们先来定义一个二叉树的结构体:
typedef struct BinaryTreeNode
{
TelemType data;
struct BinaryTreeNode *Left;
struct BinaryTreeNode *Right;
}Node;
创建二叉树,顺序为根节点->左子树->右子树:
Node* createBinaryTree()
{
Node *p;
TelemType ch;
cin>>ch;
if(ch == 0) //如果到了叶子节点,接下来的左、右子树分别赋值为0
{
p = NULL;
}
else
{
p = (Node*)malloc(sizeof(Node));
p->data = ch;
p->Left = createBinaryTree(); //递归创建左子树
p->Right = createBinaryTree(); //递归创建右子树
}
return p;
}
先序遍历:
void preOrderTraverse(Node* root)
{
if( root )
{
cout<<root->data<<' ';
preOrderTraverse(root->Left);
preOrderTraverse(root->Right);
}
}
中序遍历:
void inOrderTraverse(Node* root)
{
if( root )
{
inOrderTraverse(root->Left);
cout<<root->data<<' ';
inOrderTraverse(root->Right);
}
}
后序遍历:
void lastOrderTraverse(Node* root)
{
if( root )
{
lastOrderTraverse(root->Left);
lastOrderTraverse(root->Right);
cout<<root->data<<' ';
}
}
下面是二叉树的节点总数、深度、叶子节点数
//二叉树节点总数目
int Nodenum(Node* root)
{
if(root == NULL)
{
return 0;
}
else
{
return 1+Nodenum(root->Left)+Nodenum(root->Right);
}
}
//二叉树的深度
int DepthOfTree(Node* root)
{
if(root)
{
return DepthOfTree(root->Left)>DepthOfTree(root->Right)?DepthOfTree(root->Left)+1:DepthOfTree(root->Right)+1;
}
if( root == NULL )
{
return 0;
}
}
//二叉树叶子节点数
int Leafnum(Node* root)
{
if(!root)
{
return 0;
}
else if( (root->Left == NULL) && (root->Right == NULL) )
{
return 1;
}
else
{
return (Leafnum(root->Left) + Leafnum(root->Right)) ;
}
}
完整代码:
#include <iostream>
#include<cstdio>
#include<cstdlib>
using namespace std;
typedef int TelemType;
typedef struct BinaryTreeNode
{
TelemType data;
struct BinaryTreeNode *Left;
struct BinaryTreeNode *Right;
}Node;
//创建二叉树,顺序依次为中间节点->左子树->右子树
Node* createBinaryTree()
{
Node *p;
TelemType ch;
cin>>ch;
if(ch == 0) //如果到了叶子节点,接下来的左、右子树分别赋值为0
{
p = NULL;
}
else
{
p = (Node*)malloc(sizeof(Node));
p->data = ch;
p->Left = createBinaryTree(); //递归创建左子树
p->Right = createBinaryTree(); //递归创建右子树
}
return p;
}
//先序遍历
void preOrderTraverse(Node* root)
{
if( root )
{
cout<<root->data<<' ';
preOrderTraverse(root->Left);
preOrderTraverse(root->Right);
}
}
//中序遍历
void inOrderTraverse(Node* root)
{
if( root )
{
inOrderTraverse(root->Left);
cout<<root->data<<' ';
inOrderTraverse(root->Right);
}
}
//后序遍历
void lastOrderTraverse(Node* root)
{
if( root )
{
lastOrderTraverse(root->Left);
lastOrderTraverse(root->Right);
cout<<root->data<<' ';
}
}
//二叉树节点总数目
int Nodenum(Node* root)
{
if(root == NULL)
{
return 0;
}
else
{
return 1+Nodenum(root->Left)+Nodenum(root->Right);
}
}
//二叉树的深度
int DepthOfTree(Node* root)
{
if(root)
{
return DepthOfTree(root->Left)>DepthOfTree(root->Right)?DepthOfTree(root->Left)+1:DepthOfTree(root->Right)+1;
}
if( root == NULL )
{
return 0;
}
}
//二叉树叶子节点数
int Leafnum(Node* root)
{
if(!root)
{
return 0;
}
else if( (root->Left == NULL) && (root->Right == NULL) )
{
return 1;
}
else
{
return (Leafnum(root->Left) + Leafnum(root->Right)) ;
}
}
int main()
{
Node *root = NULL;
root = createBinaryTree();
printf("二叉树建立成功");
cout<<endl;
cout<<"二叉树总节点数为:"<<Nodenum(root)<<endl;
cout<<"二叉树深度为:"<<DepthOfTree(root)<<endl;
cout<<"二叉树叶子节点数为:"<<Leafnum(root)<<endl;
cout<<"前序遍历结果:"<<endl;
preOrderTraverse(root);
cout<<endl;
cout<<"中序遍历结果:"<<endl;
inOrderTraverse(root);
cout<<endl;
cout<<"后序遍历结果:"<<endl;
lastOrderTraverse(root);
cout<<endl;
return 0;
}
非递归来实现
非递归遍历的主要思想是通过循环与栈这种数据结构来解决遍历二叉树的问题。
定义二叉树的结构体:
struct TreeNode {
TreeNode(int val_) :val(val_), left(NULL), right(NULL) {
}
TreeNode* left;
TreeNode* right;
int val;
};
先序遍历:
首先访问根节点,再分别访问左右子节点;非递归前序遍历用栈来模拟遍历过程:
- 首先将根节点入栈
- 开始循环,访问根节点,再将根节点出栈
- 根节点左子节点入栈,最后右子节点入栈,完成一次循环
- 接下来循环进行先前3个步骤的处理直到栈为空则完成遍历。
void PreOrderNonRecursive(TreeNode* root) {
stack<TreeNode*> sta;
if (root == NULL)
return;
sta.push(root);
while (!sta.empty()) {
TreeNode* pNode = sta.top();
cout << pNode->val << endl;
sta.pop();
if (pNode->right != NULL)
sta.push(pNode->right);
if (pNode->left != NULL)
sta.push(pNode->left);
}
}
中序遍历:
首先访问左子节点,再访问根节点,最后访问右子节点;同样借助栈来进行遍历:
- 由于最先访问左子节点,所以需要先访问最左边的节点,先将根节点入栈,用一个中间变量记录每次入栈节点,以便判断其是否有左子节点
- 如果中间入栈的节点有左子节点,那么继续入栈其左子节点,直到不再有左子节点为止
- 栈顶元素出栈,即访问根节点
- 判断栈顶元素是否有右子节点,若有,入栈其右子节点
- 循环2-4步骤,直到栈为空,完成遍历
void InOrderNonRecursive(TreeNode* root) {
stack<TreeNode*> sta;
if (root == NULL)
return;
sta.push(root);
TreeNode* pNode = root;
while (!sta.empty()) {
while (pNode != NULL&&pNode->left != NULL) {
pNode = pNode->left;
sta.push(pNode);
}
pNode = sta.top();
cout << pNode->val << endl;
sta.pop();
if (pNode->right != NULL) {
sta.push(pNode->right);
pNode = pNode->right;
}
else
pNode = NULL;
}
}
后序遍历:
首先遍历左右子节点,最后遍历根节点,非递归后序遍历是三种遍历方式中最复杂的一种,最重要的是:判断每个节点的左右子节点是否为NULL,或者其左右子节点是否已经被访问过,如果是,那么访问该节点。
- 边界条件检查,入栈根节点
- 记录当前栈顶元素,如果栈顶元素左右子节点均为NULL或者均已被访问,那么访问当前节点
- 否则依次入栈其右子节点和左子节点
- 循环步骤2和步骤3,直到栈为空结束
void PostOrderNonRecursive(TreeNode* root) {
if (root == NULL)
return;
stack<TreeNode*> sta;
TreeNode* curNode = root;
TreeNode* preNode = NULL;
sta.push(root);
while (!sta.empty()) {
curNode = sta.top();
if ((curNode->left == NULL&&curNode->right == NULL) ||
(preNode != NULL && (preNode == curNode->left || preNode == curNode->right))) {
cout << curNode->val << endl;
sta.pop();
preNode = curNode;
}
else {
if (curNode->right != NULL)
sta.push(curNode->right);
if (curNode->left != NULL)
sta.push(curNode->left);
}
}
}