首先讲的是前序遍历的 非递归实现,代码如下:
//前序遍历 (栈)
void previousOrder(Bnode* root) { //Bnode是树节点类型
Stack S; //这个是一个栈,自己手写的栈
initStack(S); //初始化栈
pushStack(S, *root); /*首先将根节点压入栈,因为对于我们前序遍历来说
遍历节点和处理节点都是同一个 */
Bnode cur; //记录当前的栈顶元素
while (!isEmpty(S)) //只要栈不为空,就一直进行循环,
//栈为空的话,就代表着没有节点需要进行遍历了
{
popStack(S, cur); //首先将压入的根节点出栈,因为目前只有这一个节点
cout << cur.data << " "; //根据先序规则,先进行打印, 这里是 中
if (cur.rchild != NULL) { //这里是 右
pushStack(S, *cur.rchild);
} /*这里先判断右子树是否为空,然后入栈
因为栈的特性是先进后出,所以在栈顶的元素会被优先
处理,所以左子树要后入栈*/
if (cur.lchild != NULL) { //这里是 左
pushStack(S, *cur.lchild);
}
}
}
看过代码的同学知道,右子树先进栈,然后在判断左子树,这是为什么呢? 首先 常规 前序遍历的 规则是 (中 左 右)三个顺序,但是用栈实现之后,他的规则 变成了 (中 右 左) 这里的左右为什么换位置了?,因为我们需要先处理左每一次遇到的左子树,所以因为栈的特殊性(LIFO)先进后出,我们应该先将右子树压入栈,这样左子树出栈的时候就会被先处理。
上面说的 (中 左 右) 中 :打印输出, 左: 递归左子树 , 右: 遍历右子树
所有的遍历的顺序就是按照 写递归的顺序来排序的,这只是个顺序,而非按照递归方法进行编写,比如 在前序遍历的递归实现时 ,举个例子:
void prePrint(Tree* root){ //这个就是前序遍历的递归函数
cout<<"这里打印遍历的节点"; //这里是 中
prePrint(root->lchild); //遍历左子树, 这里是 左
prePrint(root->rchild); //遍历右子树, 这里是 右
//=============================================
//这里的顺序就是 中 左 右 所以,(中 左 右) 就是这么来的
}
中序遍历和后序遍历 是一样的方法
后序遍历的非递归实现,为什么要先讲后序而不是中序呢,因为后序遍历的实现只用在先序代码的基础上改动一下就可以了,代码如下:
//后序遍历 (栈)
void backOrder(Bnode* root,vector<DataType>& vec) {
Stack S; //跟前序遍历一样,定义一个栈
initStack(S); //初始化栈
pushStack(S, *root); //将根节点压入栈
Bnode cur; //记录当前的栈顶元素
while (!isEmpty(S))
{
popStack(S, cur);
//==========================================
vec.push_back(cur.data); //中,这里不进行输出了,而是把数据放入容器中,方便反转
if (cur.lchild != NULL) { //左,左子树就是左
pushStack(S, *cur.lchild);
}
if (cur.rchild != NULL) { //右,右子树就是右
pushStack(S, *cur.rchild);
}
}
reverse(vec.begin(), vec.end()); //reverse是全局函数,进行反转操作
for (const auto& t : vec) {
cout << t << " ";
}
//===============================================
}
上面这段代码跟前序遍历的代码唯一的差别就是,我用了一个vector容器还有入栈的顺序改变了,最后还加了一个反转数据的操作;
正常来说,前序遍历的规则是 (中 左 右),而后序遍历的规则是 (左 右 中),而前序遍历需要变成后序遍历需要调整 左右的顺序,调整后(中 右 左),进行反转(左 右 中)
奇迹的发现,我们只是进行了左右的调整和反转就把前序遍历的规则变成了后序遍历,所以代码就可以向上面那么修改, 只需要修改入栈的顺序,先入左子树,然后右子树,最后在进行反转,就可以得到 后序遍历的非递归了
中序遍历的 非递归实现 ,代码如下:
//中序遍历 (栈)
void centerOrder(Bnode* root) {
Stack S;
initStack(S);
Bnode* cur = root; //这里我用了一个指针来记录当前节点
while (!isEmpty(S) || cur != NULL) //栈不为空一直循环
{
if (cur != NULL) { //当前节点也不为空
pushStack(S, *cur);
cur = cur->lchild;
}
else { //当前节点为空的话
popStack(S, cur); //出栈
cout << cur->data << " "; //输出
cur = cur->rchild; // 指向栈中的栈顶元素(他是上一个有效节点)的右子树
}
}
}
这里中序遍历其实和先序后序都不太一样,所以单独来看,
根据中序遍历的规则 (左中右) 我们就应该知道,应该把左子树遍历完毕全部压入栈,才能开始去处理右子树,所以我定义了一个指针来记录当前的节点状态,如果当前节点不为空,那么就依次进栈,一直到他为NULL为止,就代表当前的节点的左子树已经到最左边了,然后让cur指向 栈顶元素,输出 数据,然后出栈pop(已经不需要栈顶那个元素了,所以出栈),cur已经指向刚刚出栈的元素(出栈的元素不是为NULL,他是有效的)然后cur指向cur->的右子树,
然后再次进行循环,遍历左子树,一直到左子树为NULL,然后出栈。。。。。。。跟上面顺序一样,所以一直到栈空,该树就中序遍历成功了
/