二叉树的递归和非递归遍历

前段时间瞄了一眼《剑指offer》上面有说要熟悉二叉树的6种遍历方式,说来你或许不信,我自己实现非递归遍历的时候硬是没有实现出来,看来自己的基本功还是有待加强的。话不多说,上代码加分析。

二叉树的实现:

这个基本上是轻车熟路了,平常写书的算法的时候都要求实现一遍,当然这里不是这篇文章的重点。

树的节点的定义和树的定义。

struct TreeNode {
    TreeNode *lChild;  //左孩子
    TreeNode *rChild; //右孩子
    EleType value;       //节点中的值
};

struct Tree {
    TreeNode *root;   //树可以直接用一个节点的指针来表示
};
树的实现。

TreeNode *CreateTreeNode (EleType k) {  //这个函数的意义是分配一个树的节点并且初始化
    TreeNode *temp = (TreeNode *) malloc (sizeof (TreeNode));
    temp->lChild = nullptr;
    temp->rChild = nullptr;
    temp->value = k;

    return temp;
}

void CreateTree (TreeNode **root) {
    int t;

    cin >> t;
    if (t != 0) {   //为零的话代表其是叶节点
        *root = CreateTreeNode (t);
        CreateTree (&((*root)->lChild));//利用递归实现树
        CreateTree (&((*root)->rChild));
    }
}
二叉树的前序遍历:

二叉树的前序遍历的递归实现。

void PreTravelTree (TreeNode *node) {
    if (node != nullptr) {
        cout << node->value << " ";
        PreTravelTree (node->lChild);
        PreTravelTree (node->rChild);
    }
}
二叉树的前序遍历的非递归实现。

void PreTravelTreeNonRecur (TreeNode *node) {
    stack nodeStack;
    TreeNode *cur = node;

    while (cur != nullptr || !nodeStack.empty ()) {
        while (cur != nullptr) {
            cout << cur->value << " ";
            nodeStack.push (cur);
            cur = cur->lChild;
        }
        if (!nodeStack.empty ()) {
            cur = nodeStack.top ();
            nodeStack.pop ();
            cur = cur->rChild;
        }
    }
二叉树的中序遍历:

二叉树的中序遍历的递归实现。

void MidTravelTree (TreeNode *node) {
    if (node != nullptr) {
        MidTravelTree (node->lChild);
        cout << node->value << " ";
        MidTravelTree (node->rChild);
    }
}
二叉树的中序遍历的非递归实现。

void MidTravelTreeNonRecur (TreeNode *node) {
    stack nodeStack;
    TreeNode *cur = node;

    while (cur != nullptr || !nodeStack.empty ()) {
        while (cur != nullptr) {
            nodeStack.push (cur);
            cur = cur->lChild;
        }
        if (!nodeStack.empty ()) {
            cur = nodeStack.top ();
            cout << cur->value << " ";
            nodeStack.pop ();
            cur = cur->rChild;
        }
    }
}
二叉树的后序遍历:

二叉树的后序遍历的递归实现。

void PosTravelTree (TreeNode *node) {
    if (node != nullptr) {
        PosTravelTree (node->lChild);
        PosTravelTree (node->rChild);
        cout << node->value << " ";
    }
}
二叉树的后序遍历的非递归实现。

这个是这篇文章的重点了,由于二叉树的后序遍历不同于前序遍历和中序遍历,必须要使用某种方式标记出左右字节点是否已经访问过了,那么在这里其实有两种方式,一种方式是改变底层的数的节点的数据结构,加上访问标记。第二种就是使用不同的进栈方式,在父节点进栈的时候同时将两个子节点进栈。下面先介绍第一种方式,改变底层的数据结构。

(底层数据结构只是加上了visited的字段,并没有其他的不同,不再赘述)

void PosTravelTreeNonRecurSec (TreeNodePlus *node) {
    stack nodeStack;
    TreeNodePlus *cur = node;

    while (cur != nullptr || !nodeStack.empty ()) {
        while (cur != nullptr) {  //在这个while循环当中,只将不是nullptr同时没有访问过的节点加入nodeStack
            cur->visited++;
            nodeStack.push (cur);
            if (cur->lChild == nullptr) {
                break;
            }else if (cur->lChild->visited > 0) {
                break;
            }else {
                cur = cur->lChild;
            }
        }
        if (!nodeStack.empty ()) { //进入这里就说明其子节点的一个孩子或者两个孩子已经访问过了(这里有一个特殊情况
            cur = nodeStack.top ();//就是子节点为nullptr也可以进入这里,但是子节点为nullptr也代表访问过了)
            cur->visited++;
            if (cur->visited == 3) {  //说明这个节点的左右子节点已经访问过了,那么就可以将这个节点打印出来
                cout << cur->value << " ";
                nodeStack.pop ();
                cur = nullptr;  //将其设置为nullptr那么下个循环就可以跳过上面那么while循环,处理这个节点的父节点。
            }else {
                cur = cur->rChild;//说明这个节点的右节点还没有访问,那么把当前节点的值设置为右节点。
            }
        }
    }
}
第二种方式就是使用不同的进栈方式。

void PosTravelTreeNonRecur (TreeNode *node) {
    stack> nodeStack; //这个栈的节点是一个pair
    TreeNode *cur = node;

    nodeStack.push (make_pair (cur , false));
    bool visited;
    while (!nodeStack.empty ()) {
        cur = nodeStack.top().first;
        visited = nodeStack.top().second;
        nodeStack.pop ();

        if (cur == nullptr)
            continue;
        if (visited)
            cout << cur->value << " ";
        else {  //使用精心设计的进栈方式,根节点放在最下面,然后再是右节点,再是坐节点,这样就保证取出来的时候
            nodeStack.push (make_pair(cur , true));  //左节点可以最先取出来。同时,其他两种的遍历方式的非递归实现
 nodeStack.push (make_pair(cur->rChild , false)); //也可以采用这种进栈方式,但是缺点是效率稍微低了一些,进栈
            nodeStack.push (make_pair(cur->lChild , false)); //次数多了。
        }
    }
}




你可能感兴趣的:(数据结构)