二叉树的遍历( )是指从根节点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次。
这里有两个关键词:访问 和 次序。
访问其实是要根据实际的需要来确定做什么,比如对每个结点进行相关计算,输出打印等,它算是一个抽象操作。在这里我们可以简单地假定就是输出结点的数据信息。
二叉树的遍历次序不同于线性结构,一般的线性结构最多也就是从头至尾、循环、双向等简单的遍历方式。树的结点之间不存在唯一的前驱和后继关系,在访问一个结点后,下一个被访问的结点面临着不同的选择,由于选择方式的不同,遍历的次序也就完全不同了。
二叉树遍历方法
二叉树的遍历方式可以很多,如果我们限制了从左到右的习惯方式,那么主要就分为四种:
- 前序遍历
- 中序遍历
- 后序遍历
- 层序遍历
前序遍历
规则是若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。
如下图,遍历顺序为:ABDGHCEIF。
接下来我们以下图为例对前序遍历过程做以分析:
遍历步骤:
- 先访问根节点A,结果为A
- 前序遍历根节点A的左子树,左子树的根节点是B,此时结果为AB
- 继续前序遍历以B为根节点的左子树,此时的根节点是D,结果为ABD
- 继续遍历以D为根节点的左子树H,此时结果为ABDH
- 节点H不存在左子树,则遍历以D为根节点的右子树,此时结果为ABDHI
- 递归遍历以B为根节点右子树E,由于E存在左子树J,此时结果为ABDHIE
- E存在左子树,此时结果为ABDHIEJ。此时根节点A的左子树遍历完成,开始遍历右子树
- 右子树根节点为C,此时结果为ABDHIEJC
- 以C为根节点的子树存在左节点F,结果变为ABDHIEJCF
- 节点F不存在子节点,遍历C的右节点,前序遍历最后结果为ABDHIEJCFG
中序遍历
规则是若树为空,则空操作返回,否则从根结点开始(注意并不是先访问根结点),中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。
如下图,遍历的顺序为:GDHBAEICF
接下来我们以下图为例对中序遍历过程做以分析:
遍历步骤:
- 根节点A存在左子树,先中序遍历以节点B为根节点的左子树
- 节点B存在左子树,中序遍历以节点D为根节点的左子树
- 节点D存在左叶子节点H,此时结果为HD
- 节点D存在右叶子节点I,此时结果为HDI
- 以节点B为根节点的左子树遍历完成,此时访问根节点B,结果为HDIB
- 访问B节点的右子树,右子树根节点存在左子树J,此时结果为HDIBJE
- 根节点A的左子树访问完成,访问根节点A,结果变为HDIBJEA
- 遍历根节点A的右子树,右子树存在左叶子节点F,此时结果为HDIBJEAF
- 然后访问右子树的根节点C,中序遍历最后结果为HDIBJEAFCG
后序遍历
规则是若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点。
如下图,遍历的顺序为:GHDBIEFCA
接下来我们以下图为例对后序遍历过程做以分析:
遍历步骤:
- 根节点A存在左子树,先后序遍历以节点B为根节点的左子树
- 节点B存在左子树,后序遍历以节点D为根节点的左子树
- 节点D存在左叶子节点H,右叶子节点I,此时结果为HID
- 节点B存在右子树,后序遍历以节点E为根节点的右子树
- 节点E存在左叶子节点,不存在右叶子节点,此时结果为HIDJE
- B节点的左子树、右子树遍历完成,访问B节点,结果为HIDJEB
- 根节点的左子树遍历完成,后序遍历根节点的右子树,结果为HIDJEBFGC
- 最后访问根节点,后序遍历最后结果为HIDJEBFGCA
层序遍历
规则是若树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。
如下图,遍历的顺序为:ABCDEFGHI
接下来我们以下图为例对层序遍历过程做以分析:
遍历步骤:
- 从第一层根节点开始访问,结果为A
- 访问第二层,从左到右,结果为ABC
- 访问第三层,结果为ABCDEFG
- 访问第四层,层序遍历结果为ABCDEFGHIJ
前序遍历算法
递归实现
/*
* 前序遍历的递归实现,我们直接根据定义,首先先访问根节点,
* 然后前序遍历左子树,接着前序遍历右子树。
* 上述的每个前序遍历就是一个递归的过程。
*/
void preOrder()
{
preOrder(_root);
cout << endl;
}
void preOrder(BSTNode* node)
{
if (node != nullptr)
{
cout << node->_data << " ";
preOrder(node->_left);
preOrder(node->_right);
}
}
非递归实现
/*
* 前序遍历的非递归实现,我们需要借助栈这个数据结构,由于前序遍历是
* 先访问根节点,然后前序遍历左子树,接着前序遍历右子树。
* 那么非递归实现的话,我们先压入根节点,然后打印栈顶元素,但是由于
* 出栈和入栈的顺序是相反的,因此我们接着需要先压入右孩子,再压入左孩子
* 然后打印栈顶元素,继续压入左孩子的右孩子,左孩子的左孩子···
* 上述便是迭代循环的过程。
*/
void nonpreOrder()
{
if (_root == nullptr)
{
return;
}
stack <BSTNode*> stack;
stack.push(_root);
while (!stack.empty())
{
BSTNode* top = stack.top();
cout << top->_data << " ";
stack.pop();
if (top->_right != nullptr)
{
stack.push(top->_right);
}
if (top->_left != nullptr)
{
stack.push(top->_left);
}
}
cout << endl;
}
中序遍历算法
递归实现
/*
* 中序遍历的递归实现,根据定义,我们首先中序遍历左子树,
* 再打印根节点,然后再中序遍历右子树。
* 上述的每个中序遍历就是一个递归的过程。
*/
void inOrder()
{
inOrder(_root);
cout << endl;
}
void inOrder(BSTNode* node)
{
if (node != nullptr)
{
inOrder(node->_left);
cout << node->_data << " ";
inOrder(node->_right);
}
}
非递归实现
/*
* 中序遍历的非递归实现,我们首先应该一直向左遍历,将结点压栈,
* 直到结点为空,然后我们打印该节点值,并将其出栈,并继续遍历
* 该节点的右子树,继续上述过程。
*/
void noninOrder()
{
if (_root == nullptr)
{
return;
}
stack<BSTNode*> stack;
BSTNode* top = _root;
while (!stack.empty() || top != nullptr)
{
if (top != nullptr)
{
stack.push(top);
top = top->_left;
}
else
{
top = stack.top();
cout << top->_data << " ";
stack.pop();
top = top->_right;
}
}
cout << endl;
}
后序遍历算法
递归实现
/*
* 后序遍历的递归实现,我们只需先后序遍历左子树,再后序遍历右子树
* 最后打印根节点值即可。
* 上述的每个后序遍历就是一个递归的过程。
*/
void lastOrder()
{
lastOrder(_root);
cout << endl;
}
void lastOrder(BSTNode* node)
{
if (node != nullptr)
{
lastOrder(node->_left);
lastOrder(node->_right);
cout << node->_data << " ";
}
}
非递归实现
/*
* 后序遍历的非递归实现,由于其根节点最后打印,因此我们需要借助两个栈来完成,
* 因为我们需要借助一个辅助栈来保存其父节点,而另一个栈则保存我们的结果集。
* 注意压栈顺序,本来我们应该先压右孩子,再压左孩子。但是我们使用另一个结果栈
* 来存储结果集,辅助栈中的元素最终是出栈并压入结果栈的,因此,我们程序中
* 应该先压入左孩子,再压入右孩子。
* 然后每次循环将栈顶元素出栈并压入结果栈中。
* 最后,结果栈中所存储的就是我们所需的后序遍历结果集。我们打印出栈中所有的
* 元素即可
*/
void nonlastOrder()
{
if (_root == nullptr)
{
return;
}
stack<BSTNode*> Stack;
stack<BSTNode*> StackRes;
BSTNode* top = _root;
Stack.push(top);
while (!Stack.empty())
{
top = Stack.top();
StackRes.push(top);
Stack.pop();
if (top->_left != nullptr)
{
Stack.push(top->_left);
}
if (top->_right != nullptr)
{
Stack.push(top->_right);
}
}
while (!StackRes.empty())
{
top = StackRes.top();
cout << top->_data << " ";
StackRes.pop();
}
cout << endl;
}
层序遍历算法
递归实现
/*
* 层序遍历不同于之前讲解的前序、中序、后序遍历的深度优先遍历,而它是一种典型的
* 广度优先遍历算法,那么对于层序遍历的递归实现,我们必须要获取到该树的层数,
* 才能对递归进行一个有效的控制
* 整体思路就是,我们在API接口中,向递归函数循环传入层数(0-n)
* 在递归函数中,层数为零,我们直接打印其值即可,若层数不为零,我们就递归
* 地继续向下遍历。
* 如何向下继续遍历?我们每次递归遍历就将层数减一即可,那么比如我们遍历第3层
* 元素,那么传入的参数为3,我们递归调用函数,不断的将参数减一,当减到0时便是
* 已经遍历到第三层了,参数减到0,递归结束条件满足,我们直接打印其值即可。
*/
void levelOrder()
{
int level = Treelevel(); // 求层数
for (int i = 0; i < level; ++i)
{
levelOrder(_root, i);
}
}
void levelOrder(BSTNode* node, int level)
{
if (node == nullptr)
{
return;
}
if (level == 0)
{
cout << node->_data << " ";
}
else
{
levelOrder(node->_left, level - 1);
levelOrder(node->_right, level - 1);
}
}
int Treelevel(BSTNode* node)
{
if (node == nullptr)
{
return 0;
}
int left = Treelevel(node->_left);
int right = Treelevel(node->_right);
return (left > right ? left : right) + 1;
}
非递归实现
/*
* 层序遍历的非递归实现,正常思路我们先遍历左子树,再遍历右子树,由于是
* 层序遍历,从上到下,从左到右,那么从左到右刚好满足队列的性质,先入先出
* 那么我们需要借助一个队列来保存我们遍历到的元素。
* 首先压入根节点。
* 每次遍历我们拿到队列的首元素,然后遍历其左孩子,并入队;然后遍历其右孩子
* 并入队,然后将队首元素打印并出队,继续迭代直到队列为空。
*/
void nonlevelOrder()
{
queue<BSTNode *> Nodequeue;
if (_root == nullptr)
{
return;
}
Nodequeue.push(_root);
while (!Nodequeue.empty())
{
BSTNode* front = Nodequeue.front();
if (front->_left != nullptr)
{
Nodequeue.push(front->_left);
}
if (front->_right != nullptr)
{
Nodequeue.push(front->_right);
}
cout << front->_data << " ";
Nodequeue.pop();
}
cout << endl;
}