二叉树的四种遍历方式:递归、非递归+栈、Morris(后序非递归还有一种单栈和双栈的不同版本)

本文参考:
参考文章1
参考文章2
代码中加入了一些自己的理解

/* 二叉树的四种遍历方式
*/
#include <iostream>
#include <stack>
using namespace std;

// 二叉树节点的定义
class TreeNode{

public:

    char val;
    //int val;
    TreeNode *left, *right;
    TreeNode(int val){
        this->val = val;
        this->left = this->right = NULL;
    }
};

// 递归方式,每个结点只遍历一次,时间复杂度O(1),递归最多调用n次,空间复杂度O(n)

// 先序:根-》递归左-》递归右
void preOrder(TreeNode* root){
    if(root==NULL)
        return;
    cout << root->val << endl;
    preOrder(root->left);
    preOrder(root->right);
}

// 中序:递归左-》根-》递归右
void inOrder(TreeNode* root){
    if(root==NULL)
        return;
    inOrder(root->left);
    cout << root->val << endl;
    inOrder(root->right);
}
// 后序:递归左-》递归右-》根
void postOrder(TreeNode* root){
    if(root==NULL)
        return;
    postOrder(root->left);
    postOrder(root->right);
    cout << root->val << endl;
}

// 非递归,使用栈,每个节点只需遍历一次,时间复杂度O(n),使用栈,只需压入或弹出各一次,空间复杂度O(n)

// 先序:根压入,从栈顶弹出结点,并访问,
// 压入当前右孩子,压入当前左孩子,则出栈顺序与先序遍历一致
void preOrder2(TreeNode* root){
    if(root==NULL)
        return;
    stack<TreeNode*> stk;
    stk.push(root);
    while(!stk.empty()){
        TreeNode* pNode = stk.top();
        stk.pop();
        cout << pNode->val << endl;
        if(pNode->right!=NULL)
            stk.push(pNode->right);
        if(pNode->left!=NULL)
            stk.push(pNode->left);
    }
}

// 中序:初始指针pNode指向根,
// 若pNode不空,则pNode压入,pNode指向当前左孩子,一直到最左,
// 若pNode为空,从栈顶弹出结点,并访问,pNode当前右孩子
void inOrder2(TreeNode* root){
    if(root==NULL)
        return;
    stack<TreeNode*> stk;
    TreeNode* pNode = root;
    while(pNode!=NULL || !stk.empty()){
        if(pNode!=NULL){
            stk.push(pNode);
            pNode = pNode->left;
        }
        else{
            pNode = stk.top();
            cout << pNode->val << endl;
            stk.pop();
            pNode = pNode->right;
        }
    }
}

// 后序,使用2个栈:设置两个栈stk, stk2;
// 将根结点压入第一个栈stk;弹出stk栈顶的结点,并把该结点压入第二个栈stk2;
// 将当前结点的左孩子和右孩子先后分别入栈stk;当所有元素都压入stk2后,依次弹出stk2的栈顶结点,并访问之。
// 第一个栈的入栈顺序是:根结点,左孩子和右孩子;压入第二个栈的顺序是:根结点,右孩子和左孩子。
// 因此,第二个栈弹出的顺序就是:左孩子,右孩子和根结点。
void postOrder2(TreeNode* root){
    if(root==NULL)
        return;
    stack<TreeNode*> stk;
    stack<TreeNode*> stk2;
    stk.push(root);
    while(!stk.empty()){
        TreeNode* pNode = stk.top();
        stk.pop();
        stk2.push(pNode);
        if(pNode->left!=NULL)
            stk.push(pNode->left);
        if(pNode->right!=NULL)
            stk.push(pNode->right);
    }
    while(!stk2.empty()){
        cout << stk2.top()->val << endl;
        stk2.pop();
    }

}

// 后序,使用1个栈:
// 每次循环开始时,当前结点更新为栈顶
// 首先向下遍历,指向之前访问的结点的指针prev为空时,或者当前访问的是prev的孩子时,继续向下访问左孩子并压入,左孩子没有则访问右孩子并压入
// 之后是从左孩子向上遍历,即当前访问的是prev的父结点时,如果父节点有右孩子,继续向下访问右孩子,并压入
// prev不为空,并且当前访问的节点也不是prev的孩子,说明已经到达叶子节点或父结点,之后输出当前节点,并弹出
// 每个步骤操作完成后,将prev更新为当前结点
void postOrder3(TreeNode* root){
    if(root==NULL)
        return;
    stack<TreeNode*> stk;
    stk.push(root);
    TreeNode* prev = NULL;//指向上次访问的节点,即为父节点或者叶节点
    while(!stk.empty()){
        TreeNode *pNode = stk.top();
        if(!prev || prev->left == pNode || prev->right == pNode){//之前没有刚问过的点,或者当前是prev的孩子,则继续向下遍历并压入
            if(pNode->left)
                stk.push(pNode->left);
            else if(pNode->right)
                stk.push(pNode->right);
        }
        else if(pNode->left==prev){//若左孩已经访问过
            if(pNode->right)//则继续访问右孩,并压入
                stk.push(pNode->right);
        }
        else{//到达叶节点,或者父节点
            cout << pNode->val << endl;
            stk.pop();
        }
        prev = pNode;
    }
}

// Morris 遍历:线索二叉树,在O(1)空间内,O(n)时间内完成遍历,通过修改叶子结点的左右空指针指向前驱或后记

// 中序:
// 1. 如果当前节点的左孩子为空,则输出当前节点并将其右孩子作为当前节点。
// 2. 如果当前节点的左孩子不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点。
//  a) 如果前驱节点的右孩子为空,将它的右孩子设置为当前节点。当前节点更新为当前节点的左孩子。
//  b) 如果前驱节点的右孩子为当前节点,将它的右孩子重新设为空(恢复树的形状)。输出当前节点。当前节点更新为当前节点的右孩子。
// 3. 重复以上1、2直到当前节点为空。
// 使用两个辅助指针,空间复杂度O(1),
// 时间复杂度O(n):n个结点二叉树有n-1条边,每条边最多有两次,一次用于找前驱,一次用于访问该结点
void inOrderMorris(TreeNode* root){
    TreeNode* cur = root;
    TreeNode* prev = NULL;
    while(cur!=NULL){
        if(cur->left == NULL){      // 1. (表示左子树已经遍历完,访问其前驱)
            cout << cur->val << endl;
            cur = cur->right;
        }
        else{
            //找到cur的前驱prev,即为cur的左孩子的最右孩子
            prev = cur->left;
            while(prev->right!=NULL && prev->right!=cur){
                prev = prev->right;
            }
            //已经找到prev
            if(prev->right==NULL){  // 2.a)
                prev->right = cur;  // 添加线索
                cur = cur->left;    // 向下左遍历继续为每个中结点找前驱
            }
            else{                           // 2.b)
                prev->right = NULL;         // 第二次遍历到中结点,删除线索
                cout << cur->val << endl;   // 打印中结点
                cur = cur->right;           // 访问右子树,右子树不再有右子树时,cur为空,程序终止
            }
        }
    }
}

// 前序:与中序相似,在于输出的顺序
// 1. 如果当前节点的左孩子为空,则输出当前节点并将其右孩子作为当前节点。
// 2. 如果当前节点的左孩子不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点。
//  a) 如果前驱节点的右孩子为空,将它的右孩子设置为当前节点。输出当前节点(在这里输出,这是与中序遍历唯一一点不同)。当前节点更新为当前节点的左孩子。
//  b) 如果前驱节点的右孩子为当前节点,将它的右孩子重新设为空。当前节点更新为当前节点的右孩子。
// 3. 重复以上1、2直到当前节点为空。
// 时间、空间复杂度与中序相同
void preOrderMorris(TreeNode* root){
    TreeNode* cur = root;
    TreeNode* prev = NULL;
    while(cur!=NULL){
        if(cur->left == NULL){
            cout << cur->val << endl;
            cur = cur->right;
        }
        else{
            prev = cur->left;
            while(prev->right!=NULL && prev->right!=cur){
                prev = prev->right;
            }

            if(prev->right == NULL){ // 与中序唯一不同之处
                cout << cur->val << endl;
                prev->right = cur;
                cur = cur->left;
            }
            else{
                prev->right = NULL;
                cur = cur->right;
            }
        }
    }
}

// 后序:稍复杂,需要建立临时结点dump,令其左孩子是root,
// 需要子过程:倒序输出某两个节点之间路径上的各个节点
// 当前节点设置为临时节点dump。
// 1. 如果当前节点的左孩子为空,则将其右孩子作为当前节点。
// 2. 如果当前节点的左孩子不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点。
//  a) 如果前驱节点的右孩子为空,将它的右孩子设置为当前节点。当前节点更新为当前节点的左孩子。
//  b) 如果前驱节点的右孩子为当前节点,将它的右孩子重新设为空。倒序输出从当前节点的左孩子到该前驱节点这条路径上的所有节点。当前节点更新为当前节点的右孩子。
// 3. 重复以上1、2直到当前节点为空。

void reverseFromTo(TreeNode* from, TreeNode* to){
    if(from==to)
        return;
    TreeNode *x = from;//x为路径头的结点
    TreeNode *y = from->right;//y为待插入到路径头的节点
    TreeNode *z;//下一带插入点
    // y不断向x头位置前插
    while(true){
        z = y->right;
        y->right = x;
        x = y;
        y = z;
        if(x == to)
            break;
    }
}

void printReverse(TreeNode* from, TreeNode* to){
    reverseFromTo(from,to);//颠倒from到to结点的路径
    TreeNode* pNode = to;
    while(true){
        cout << pNode->val << endl;
        if(pNode==from)
            break;
        pNode = pNode->right;
    }
    reverseFromTo(to,from);//恢复树的形状

}
void postOrderMorris(TreeNode* root){
    if(root == NULL)
        return;
    TreeNode* dump = new TreeNode('z');
    dump->left = root;
    TreeNode* pNode = dump;
    TreeNode* pPrev = NULL;
    while(pNode!=NULL){//当前节点不为空时
        if(pNode->left==NULL)
            pNode = pNode->right;
        else{
            //找前驱pPrev
            pPrev = pNode->left;
            while(pPrev->right!=NULL && pPrev->right!=pNode){
                pPrev = pPrev->right;
            }
            //前驱pPrev已找到
            if(pPrev->right==NULL){
                pPrev->right = pNode;
                pNode = pNode->left;
            }
            else{
                pPrev->right = NULL;
                printReverse(pNode->left,pPrev);
                pNode = pNode->right;
            }
        }
    }

}




int main()
{

    TreeNode* a = new TreeNode('A');
    TreeNode* b = new TreeNode('B');
    TreeNode* c = new TreeNode('C');
    TreeNode* d = new TreeNode('D');
    TreeNode* e = new TreeNode('E');
    TreeNode* f = new TreeNode('F');
    TreeNode* root = a;
    root->left = b;
    root->right = c;
    b->left = d;
    b->right = e;
    c->left = f;




    // 前序:ABDECF
    // 中序:DBEAFC
    // 后序:DEBFCA
    cout << "先序遍历:递归"<< endl;
    preOrder(root);
    cout << "中序遍历:递归"<< endl;
    inOrder(root);
    cout << "后序遍历:递归"<< endl;
    postOrder(root);
    cout << "先序遍历:栈"<< endl;
    preOrder2(root);
    cout << "中序遍历:栈"<< endl;
    inOrder2(root);
    cout << "后序遍历:双栈"<< endl;
    postOrder2(root);
    cout << "后序遍历:单栈"<< endl;
    postOrder3(root);
    cout << "中序遍历:Morris"<< endl;
    inOrderMorris(root);
    cout << "后序遍历:Morris"<< endl;
    postOrderMorris(root);


}

可以使用 Morris 遍历算法。该算法利用了线索二叉树的思想,将每个节点的右子树指向其后继节点,从而实现不需要使用遍历。 具体实现步骤如下: 1. 将当前节点设为根节点。 2. 如果当前节点的左子树为空,则输出当前节点的值,并将其右子节点作为当前节点。 3. 如果当前节点的左子树不为空,在当前节点的左子树中找到当前节点在中序遍历中的前驱节点。 a. 如果前驱节点的右子节点为空,将它的右子节点设置为当前节点,当前节点更新为其左子节点。 b. 如果前驱节点的右子节点为当前节点,将它的右子节点重新设为空(恢复树的形状),输出当前节点的值,当前节点更新为其右子节点。 4. 重复步骤 2 和步骤 3,直到遍历完整棵树。 实现代码如下: ```python class TreeNode: def __init__(self, val=0, left=None, right=None): self.val = val self.left = left self.right = right def morris_traversal(root: TreeNode) -> list: res = [] curr = root while curr: if curr.left is None: res.append(curr.val) curr = curr.right else: # 找到当前节点的前驱节点 pre = curr.left while pre.right and pre.right != curr: pre = pre.right if pre.right is None: # 将前驱节点的右子节点指向当前节点,以便后续恢复树的形状 pre.right = curr curr = curr.left else: # 恢复树的形状,输出当前节点的值,当前节点更新为其右子节点 pre.right = None res.append(curr.val) curr = curr.right return res ``` 注意:这种算法会改变树的形状,因此不适用于需要多次遍历的情况。另外,由于需要修改树的结构,所以该算法不是完全不使用,而是用了隐式的(即树的右子节点)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值