二叉树的三种遍历算法的递归实现
特别说明:函数形参中的 void(*visit)(BinTreeNode *p) 代表:采用一个visit的指针指向某个函数,该函数的形参为BinTreeNode *p,该函数的实现体中可以对结点进行处理,传参时将外部函数名传入即可
前、中、后的差别仅在于 visit(subTree) 该语句的位置不同
1、前序遍历
template<class T>
void PreOrder(BinTreeNode<T> *subTree, void(*visit)(BinTreeNode<T> *p)) {
if (subTree != nullptr) {
visit(subTree);
PreOrder(subTree->leftChild, visit);
PreOrder(subTree->rightChild, visit);
}
}
2、中序遍历
template<class T>
void InOrder(BinTreeNode<T> *subTree, void (*visit)(BinTreeNode<T> *p)) {
if (subTree != nullptr) {
InOrder(subTree->leftChild, visit);
visit(subTree);
InOrder(subTree->rightChild, visit);
}
}
3、后序遍历
template<class T>
void PostOrder(BinTreeNode<T> *subTree, void(*visit)(BinTreeNode<T> *p)) {
if (subTree != nullptr) {
PostOrder(subTree->leftChild, visit);
PostOrder(subTree->rightChild, visit);
visit(subTree);
}
}
二叉树的三种遍历算法的非递归实现
把递归过程改为非递归过程(亦称迭代),一般需要一个工作栈,记录遍历时的回退路径。
1、两种前序遍历的非递归写法
第一种:利用栈存储当前结点的右结点,接着就进入当前结点的左结点进行处理,处理结束后取栈中对应的右结点
template<class T>
void PreOrder(BinTreeNode<T> *subTree, void(*visit)(BinTreeNode<T> *p)) {
stack <BinTreeNode<T>*> s;
s.push(nullptr); //栈底存放空指针作为结束标志
BinTreeNode<T> *t = subTree;
while (t != nullptr) {
visit(t); //访问结点
if (t->rightChild != nullptr) { //右结点入栈
s.push(t->rightChild);
}
if (t->leftChild != nullptr) { //处理左结点
t = t->leftChild;
}
else {
t = s.top(); //左结点为空
s.pop();
}
}
}
第二种:利用栈先存储右结点,后存储左结点,实现左、右的出栈顺序
template<class T>
void PreOrder(BinTreeNode<T> *subTree, void(*visit)(BinTreeNode<T> *p)) {
stack <BinTreeNode<T>*> s;
s.push(subTree);
BinTreeNode<T> *t;
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);
}
}
}
2、二叉树的层序遍历
利用队列实现层序遍历
template<class T>
void LevelOrder(BinTreeNode<T> *subTree, void(*visit)(BinTreeNode<T> *p)) {
queue<BinTreeNode<T>*> q;
BinTreeNode<T> *t;
q.push(subTree); //根结点入栈
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);
}
}
}
3、中序遍历的非递归算法
与前序遍历类似,同样使用一个栈来记录回退的路径。首先从根结点开始走到最左下角的结点,该结点的左子树为NULL,然后访问该结点的数据,再访问该结点的右子树。在右子树中重复上述流程,直到该子树遍历完。
template<class T>
void InOrder(BinTreeNode<T> *subTree, void(*visit)(BinTreeNode<T> *p)) {
stack<BinTreeNode<T>*> s;
BinTreeNode<T> *t = subTree; //遍历指针t
do
{
//第一步:遍历指针t指向最左下角的空结点
while (t != nullptr) {
s.push(t);
t = t->leftChild;
}
//到达空指针后,回退上一层结点,并行进到右结点
if (!s.empty()) {
t = s.top();
s.pop();
visit(t);
t = t->rightChild;
}
} while (p != nullptr || !s.empty());
}
4、后序遍历的非递归算法
后序遍历情况较为复杂,在遍历完左子树时不能访问根结点,需要再遍历右子树,待访问完毕后,才访问根结点。所以在栈工作记录中必须注明刚才是在左(L)还是右(R)中,故结点需要包含方向信息
遍历使用的结点定义
typedef enum {L, R} Tag;
template<class T>
struct stkNode
{
BinTreeNode<T> *ptr;
Tag tag;
stkNode(BinTreeNode<T> *p = nullptr): ptr(p),tag(L){}
};
后序遍历主函数
template<class T>
void PostOrder(BinTreeNode<T> *subTree, void(*visit)(BinTreeNode<T> *p)) {
stack<stkNode<T>> s;
stkNode<T> w;
BinTreeNode<T> *t = subTree;
do
{
//前进到最左下角
while (t != nullptr)
{
w.ptr = t;
w.tag = L;
s.push(w);
t = t->leftChild;
}
//是否继续while循环的标志,从左出来,不继续读结点,从右结点出来继续从栈中读出结点
int continuel = 1;
while (continuel && !s.empty()) {
w = s.top();
s.pop();
t = w.ptr;
switch (w.tag)
{
case L: w.tag = R;
s.push(w);
continuel = 0;
t = t->rightChild;
break;
case R:visit(t);
break;
}
}
} while (!s.empty());
}