二叉树的遍历

二叉树的遍历

二叉树是一种常见的数据结构,对于这种结构的操纵一定要烂熟于心,这篇文章想记录一下的,是二叉树的遍历操作.

实现的方式

二叉树的遍历,事实上有两种是实现方法,一种是递归,这种方法过分简单,所以这里不会讲,第二种方式是非递归,你可能会问,既然递归实现这么简单,我们为什么还要非递归版本的实现,这个问题很好,这个实际上和递归的内存开销有关,每一次递归,程序便要堆栈,每次堆栈,便会占用比非递归昂贵得多的内存空间,一旦二叉树过分地大,很容易堆栈溢出,而非递归版本的实现内存消耗相对来说会小很多,所以这种方法的意义还是非常大的.

使用的数据结构

/*
 * @struct
 * @brief 二叉树节点
 */
struct treeNode {
    treeNode *left;
    treeNode *right;
    int val;  /* 卫星数据 */
    treeNode(int x): val(x), left(nullptr), right(nullptr){}
};

先序遍历

/**
 * @brief 先序遍历,非递归版本
 */
void preOrder(const treeNode *root, void(*visit)(const treeNode*)) {
    const treeNode *p = root;
    stack<const treeNode*> nodes;
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    递归版本的先序遍历
    void preOrder(const treeNode *root, void (*visit)(const treeNode *)) {
        if (root == nullptr) return;
        visit(root);
        if (root->left != nullptr) preOrder(root->left);
        if (root->right != nullptr) preOrder(root->right);
    }
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    if (p != nullptr) nodes.push(p);
    /* 非递归版本的先序遍历是递归版本的忠实模拟 */
    while (!nodes.empty()) {
        p = nodes.top(); nodes.pop();
        visit(p);
        if (p->right != nullptr) nodes.push(p->right);
        if (p->left != nullptr) nodes.push(p->left);
    }
}

该版本是先序遍历递归版本的忠实模拟.

中序遍历

其实有很多种实现非递归中序遍历的方式,下面是一种:

/**
 * @brief 中序遍历,非递归版本
 */
void inOrder(const treeNode *root, void(*visit)(const treeNode*)) {
    const treeNode *p = root;
    stack<const treeNode*> nodes;

    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     递归版本的中序遍历
      void inOrder(treeNode *root, void (*visit)(const treeNode *)) {
        if (root == nullptr) return;
        if (root->left != nullptr) inOrder(root->left);
        visit(root);
        if (root->right != nullptr) inOrder(root->right);
      }
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

    while (!nodes.empty() || p != nullptr) {
        if (p != nullptr) { /* 先将左边部分入栈 */
            nodes.push(p);
            p = p->left;
        }
        else {
            p = nodes.top(); nodes.pop();
            visit(p);
            p = p->right; /* 访问右子树 */
        }
    }
}

唯一不好的地方在于,这种方法,其实很巧妙,如果要你迅速写出上面的代码,在不是很熟悉的情况下,有非常大的难度,所以,有了下面这个对递归版本的模拟版本.

void inOrder2(const treeNode *root, void(*visit)(const treeNode*)) {
    const treeNode *p = root;
    stack<const treeNode*> nodes;

    do {
        while (p != nullptr) { /* 将左子树全部入栈 */
            nodes.push(p);
            p = p->left;
        }

        while (!nodes.empty()) {
            p = nodes.top(); nodes.pop();
            visit(p);
            if (p->right != nullptr) { /* 左子树已经访问过了,右子树不为空 */
                p = p->right;
                break;
            }
            /* p不存在右子树,那么要退栈 */
        }
    } while (!nodes.empty());
}

这个版本好了一点,方便于理解和默写,这个版本和我们的平时思考的方式是一致的,我这里讲一下要点, do {expr;}while(condition) 这一轮迭代,相当于一次若干次递归,第一个 while 循环,将左子孙节点全部入栈,然后就开始退栈了,访问完栈顶元素之后,如果栈顶元素的右子节点不为空,那么要先访问右子树,这个时候只需要将 p 设定为右子节点,然后开始一轮新的do {expr;}while(condition) 迭代即可, 否则的话,因为栈顶元素的左子树已经访问过了,右子节点又为空,只能选择退栈.

后序遍历

后续遍历的非递归实现还是有相当大难度的.

/**
 * @brief 后序遍历,非递归版本
 */
void postOrder(const treeNode* root, void *(*visit)(const treeNode*)) {
    const treeNode *p = root, *q;
    stack<const treeNode*> nodes;
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    递归版本的后续遍历
    void postOrder(treeNode *root, void (*visit)(const treeNode *)) {
        if (root == nullptr) return;
        postOrder(root->left);
        postOrder(root->right);
        visit(root);
    }
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    do {
        while (p != nullptr) { /* 往左下走 */
            nodes.push(p);
            p = p->left;
        }
        q = nullptr;
        while (!nodes.empty()) {
            p = nodes.top(); nodes.pop();
            /* 右孩子不存在或者已经被访问过了,访问该节点 */
            if (p->right == q) {
                visit(p);
                q = p;  /* 保存刚访问过的节点 */
            }
            else {
                /* 当前节点不能访问,需要第二次进栈 */
                nodes.push(p);
                /* 左子树已经遍历过了,根据后序遍历规则,先处理右子树 */
                p = p->right;
                break;
            }
        }
    } while (!nodes.empty());
}

这个版本的实现和上面的中序遍历版本2有异曲同工之妙.每一次 do { expr; } while(condition); 循环都相当于递归版本的若干次递归. 首先让 p 节点所有的左子孙节点入栈,然后开始处理栈顶元素,如果栈顶元素的右子树还未被遍历过,让 p = p->right ,交由下一次 do { expr; } while(condition); 迭代的时候来处理. 如果已经处理过了,因为栈顶元素的左子树已经遍历过了,右子树也已经遍历过了,那么只能遍历栈顶元素,然后退栈,继续.

这里有一个和之前不太一样的点是,如果不采用辅助变量的话,我们是没法知道右子树是否已经被遍历过了的,因此需要构建一个辅助变量,记住前一次访问的节点.

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值