一、二叉树的遍历:
按照某种顺序访问二叉树中的每个结点,并使每个结点被访问一次且只被访问一次。
二、访问怎样理解?
就是对结点的增加、删除、查阅、修改或加工。(我们先简化为对结点数据域值的输出)
三、遍历的作用:
将非线性结构变成线性结构
四、遍历种类:
1、前序遍历/先根(序)遍历/先序遍历2、中 序遍历/中根(序)遍历/中序遍历3、后 序遍历/后根(序)遍历/后序遍历4、层次序遍历
五、前序遍历
1、递归算法:
template<class T>
void BinaryTree<T>::preOrder(BinTreeNode<T>* subTree, void(*visit)(BinTreeNode<T>* p))
{
if (subTree != nullptr)
{
visit(subTree);
preOrder(subTree->leftChild, visit);
preOrder(subTree->rightChild, visit);
}
}
2、非递归算法:
<1> 思路1:(如果二叉树非空,则)
(1)将二叉树的根结点作为当前结点;
(2)若当前结点非空,则先访问该节点,并将该结点进栈,再将其左孩子结点作为当前结点,重复步骤(2),直到当前结点为空为止;
(3)若栈非空,则将栈顶结点出栈,并将当前结点的右孩子结点作为当前结点;
(4)重复步骤(2)、(3),直到栈为空且当前结点为空为止。
<2>实现1:
template <class T>
void BinaryTree<T>::preOrder(void(*visit)(BinTreeNode<T>*p))
{
stack<BinTreeNode<T>*> s;
BinTreeNode<T>* t = root;
while (!s.empty() || t != 0)
{
while (t != nullptr)
{
visit(t);
s.push(t);
t = t->leftChild;
}
if (!s.isEmpty())
{
t = s.top();
s.pop();
t = t->rightChild;
}
}
}
<3>思路2:(如果二叉树非空,则)
(1)将二叉树的根结点进栈;
(2)若栈非空,则将栈顶结点出栈并对其实施访问;
(3)若该结点有右子树,则将右子树的根结点进栈;
(4)若该结点有左子树,则将左子树的根结点进栈;
(5)重复步骤(2),(3),(4),直到栈为空为止。
<4>实现2:
template<class T>
void BinaryTree<T>::preOrder(void(*visit)(BinTreeNode<T>* p))
{
stack<BinTreeNode<T>*> s;
t = root;
s.push(root);
while (!s.empty())
{
t = s.top();
s.pop();
visit(t);
if (t->rightChild != nullptr) s.push(t->rightChild);
if (t->leftChild != nullptr) s.push(t->leftChild);
}
}
六、中序遍历
1、递归算法:
template<class T>
void BinaryTree<T>::inOrder(BinTreeNode<T>* subTree, void(*visit)(BinTreeNode<T>* p))
{
if (subTree != nullptr)
{
inOrder(subTree->leftChild, visit);
visit(subTree);
inOrder(subTree->rightChild, visit);
}
}
2、非递归算法:
<1>思路:(若二叉树非空,则)
(1)将二叉树的根结点作为当前结点;
(2)若当前结点非空,则该结点进栈并将其左孩子结点作为当前结点,重复步骤(2),直到当前结点为空为止;
(3)若栈非空,则将栈顶结点出栈并作为当前结点,接着访问当前结点,再将当前结点的右孩子结点作为当前结点;
(4)重复步骤(2),(3),直到栈为空且当前结点为空为止。
<2>实现:
template<class T>
void BinaryTree<T>::inOrder(void(*visit)(BinTreeNode<T>* p))
{
stack<BinTreeNode<T>*> s;
BinTreeNode<T>* t = root;
while (!s.empty()||t!=nullptr)
{
while (t != nullptr)
{
s.push(t);
t = t->leftChild;
}
if (!s.empty())
{
t = s.top();
s.pop();
visit(t);
t = t->rightChild;
}
}
}
七、后序遍历
1、递归算法:
template<class T>
void BinaryTree<T>::postOrder(BinTreeNode<T>* subTree, void(*visit)(BinTreeNode<T>* p))
{
if (subTree != nullptr)
{
postOrder(subTree->leftChild, visit);
postOrder(subTree->rightChild, visit);
visit(subTree);
}
}
2、非递归算法:
<1>思路:每个结点需要进栈,出栈各两次。为了区别同一个结点的两次进栈,需要设置一个标志flag。
flag = 0,第一次进栈,表示该结点出栈后不能访问;
flag = 1,第二次进栈,表示该结点出栈后可以访问。
<2>实现:
template<class T>
void BinaryTree<T>::postOrder(void(*visit)(BinTreeNode<T>* p))
{
int flag;
stack<BinTreeNode<T>*> s1;
stack<int> s2;
BinTreeNode<T>* t = root;
while (!s1.empty() || t != nullptr)
{
while (t != nullptr)//"1"
{
s1.push(t);
s2.push(0);
t = t->leftChild;
}
if (!s.empty())
{
t = s1.top();
s1.pop();
flag = s2.top();
s2.pop();
if (flag == 1)
{
visit(t);
t = nullptr; //目的:跳过“1”
}
else
{
s1.push(t);
s2.push(1);
t = t->rightChild;
}
}
}
}
八、以上三种非递归遍历算法的统一
1、思路:一般从代码实现最复杂的非递归遍历算法入手,然后加以改动,即对后序遍历的非递归算法进行改动即可统一三种非递归算法;
2、实现:(只需改动
后序遍历的非递归算法代码中语句
visit(t)的位置)
template<class T>
void BinaryTree<T>::postOrder(void(*visit)(BinTreeNode<T>* p))
{
int flag;
stack<BinTreeNode<T>*> s1;
stack<int> s2;
BinTreeNode<T>* t = root;
while (!s1.empty() || t != nullptr)
{
while (t != nullptr)//"1"
{
//visit(t);(前序遍历)
s1.push(t);
s2.push(0);
t = t->leftChild;
}
if (!s.empty())
{
t = s1.top();
s1.pop();
flag = s2.top();
s2.pop();
if (flag == 1)
{
//visit(t);(后序遍历)
t = nullptr; //跳过“1”
}
else
{
//visit(t);(中序遍历)
s1.push(t);
s2.push(1);
t = t->rightChild;
}
}
}
}
九、层次序遍历
1、思路:
(1)遍历之前先将二叉树的根结点存入队列中;
(2)然后依次从队列中取出队头结点,每取出一个结点,都先访问该结点;
(3)接着分别检查该结点是否存在左、右孩子,若存在,则先后入列;
(4)如此反复,直到队列为空为止。
2、实现:
template<class T>
void BinaryTree<T>::levelOrder(void(*visit)(BinTreeNode<T>* p))
{
queue<BinTreeNode<T>*> q;
BinTreeNode<T>* t = root;
if (t != nullptr) q.push(t);
while (!q.empty())
{
t = q.front();
q.pop();
visit(t);
if (t->leftChild != nullptr) q.push(t->leftChild);
if (t->rightChild != nullptr) q.push(t->rightChild);
}
}
<PS>:
以上算法实现代码中使用的是STL中的stack,便于直接使用。
以上材料的大部分内容来自本人的数据结构老师-黄煜廉老师。