手把手教数据结构与算法:简单二叉树(递归和迭代实现三种遍历)

个人主页:

想转码的电筒人

专栏:

手把手教数据结构与算法

目录

二叉树

基本概念

树的基本术语

二叉树的定义

​编辑

二叉树的形态

基本形态

特殊形态

完全二叉树

满二叉树

完美二叉树

扩充二叉树

二叉树的常见基本操作

二叉树的基本性质

二叉树的存储方式

顺序存储

完全二叉树顺序存储

非完全二叉树的顺序存储

链接存储

二叉树类的应用

递归实现二叉树

问题描述

输入

输出

样例

解题思路

代码实现

结点类(Node)

二叉树类(Binary Tree)

公有函数

构造函数

基本函数

createTree()函数 

height()函数

size()函数 

遍历函数

preOrder()函数

midOrder 函数

postOrder 函数 

makeTree()函数

clear()函数

完整代码(递归法)

递归算法的问题

二叉树遍历迭代器

问题描述

输入

输出

样例

解题思路

前序遍历迭代器

中序遍历迭代器

后序遍历迭代器

代码实现

树结点类(Node)

辅助结点类(StNode)

链式栈类(linkStack)

 TreeIterator类

前序遍历(Preorder) 

中序遍历(InOrder)

后序遍历(PostOrder)

完整代码(迭代器)

附录

分类专栏

本专栏上一节


二叉树

基本概念

树的基本术语

结点的度:子结点或非空子树的个数

树的度:树中所有结点的度的最大值

叶结点:树中度为0的结点

中间结点:树中叶结点以外的结点,亦称内部结点

兄弟结点:父结点相同的结点彼此是兄弟结点

结点的层次:根结点在第1层;如果结点的层次是𝑘 (𝑘≥1) ,则其子结点都在第𝑘+1层。亦称结点的深度

结点的高度:叶结点的高度等于1;中间结点的高度等于其所有子结点的高度的最大值加1

树的高度:根结点的高度,亦称树的深度

有序树:树中各结点的子树从左向右依次排列,不能交换次序;否则称作无序树

森林:零个或多个互不相交(独立)的树的集合

二叉树的定义

二叉树(Binary Tree)

二叉树是一种树形数据结构,其中每个节点最多有两个子节点,通常称为左子节点和右子节点,因此二叉树的度为2。二叉树可以为空,或者由一个根节点和零个、一个或两个子树组成,这些子树也是二叉树。

二叉树的定义可以用递归的方式描述:

  1. 一个节点包含一个数据元素以及指向其左子树和右子树的指针。
  2. 左子树和右子树也是二叉树。
  3. 二叉树的子树可以为空,也可以包含一个或多个节点。

二叉树可以是有序的,也可以是无序的,它被广泛应用于表达式计算,数据压缩和编码,字符串处理和搜索中。

二叉树的形态

基本形态

从上面二叉树的递归定义可以看出,二叉树或为空,或为一个根结点加上两棵左右子树,因为两棵左右子树也是二叉树也可以为空,所以二叉树有5种基本形态:

特殊形态
完全二叉树

完全二叉树满足下述三个条件:

1.从第1层到第𝑑−2层全是度为2的中间结点

2.第𝑑层的结点都是叶结点,度为0

3.在第𝑑−1层,各结点的度从左向右单调非递增排列,同时度为1的结点要么没有,要么只有一个且该结点的左子树非空

下图为树的高度不超过3的所有完全二叉树

满二叉树

由度为0的叶结点和度为2的中间结点构成的二叉树,树中没有度为1的结点(国外定义)

国内定义满二叉树等同上文所述的完全二叉树

完美二叉树

对于高度(深度)为d≥1 的完全二叉树,如果第d-1层所有结点的度都是2,则该树是一个完美二叉树

扩充二叉树

扩充二叉树是二叉树中的一种,是指在二叉树中出现空子树的位置增加空树叶,所形成的二叉树

二叉树的常见基本操作

  1. 插入节点(Insert Node):在二叉树中插入新的节点,通常需要遵循特定的插入规则,如二叉搜索树中保持节点值的有序性。

  2. 删除节点(Delete Node):从二叉树中删除指定节点,需要考虑不同情况下的处理方法,如叶子节点、有一个子节点的节点、有两个子节点的节点等。

  3. 查找节点(Search Node):在二叉树中查找指定值的节点,可以采用递归或迭代的方式实现。

  4. 遍历(Traversal):遍历二叉树是指按照一定顺序访问树中的所有节点。常见的遍历方式包括: 

    1. 前序遍历(Preorder Traversal):根-左-右

    2. 中序遍历(Inorder Traversal):左-根-右

    3. 后序遍历(Postorder Traversal):左-右-根

    4. 层序遍历(Level Order Traversal):按层从左到右逐个访问节点

  5. 计算树的高度(Calculate Height):计算二叉树的高度或深度,即树中从根节点到最远叶子节点的最长路径的长度。

  6. 判断是否为空树(IsEmpty):检查二叉树是否为空,即是否包含任何节点。

  7. 判断树的相等性(Check Tree Equality):比较两棵二叉树是否结构相同且对应节点的值相等

二叉树的基本性质

1.二叉树的第i层上至多有2^(i-2)(i≥1)个节点

2.深度为h的二叉树中至多含有2^(h-1)个节点

3.若在任意一棵二叉树中,有n0个叶子节点,有n2个度为2的节点,则必有n0=n2+1

4.具有n个节点的满二叉树深为log2(n+1)

5.非空满二叉树中叶结点数等于中间结点数加1(满二叉树定理)

6.完全二叉树有n个结点(n≥1),按层次从左向右连续编号。树中任一结点k (1≤k≤n)满足以下性质:

(1)如果2k≤n,则结点 k 的左子结点是 2k,否则没有左子结点

(2)如果2k+1≤n ,则结点 k 的右子结点是2k+1 ,否则没有右子结点

(3)如果 k>1,则结点 k 的父结点是 ⌊k/2⌋

二叉树的存储方式

顺序存储
完全二叉树顺序存储

完全二叉树所有结点可以分层从左向右连续编号,可用一组地址连续的存储单元(顺序表)存储二叉树的各个结点

非完全二叉树的顺序存储

(1)结点编号:树根的索引为1;设结点的编号为𝑘 (𝑘≥1),如果其左子树非空,则左子结点的编号为2𝑘;如果右子树非空,则右子结点为2𝑘+1

(2)顺序存放:用一组地址连续的存储单元存储二叉树的各个结点

链接存储

使用链表存放二叉树,比顺序存储能更有效地表达二叉树的非线性逻辑结构

二叉树类的应用

下面让我们自己动手用递归的方法实现一棵二叉树,并完成求高度,规模以及遍历等操作。

递归实现二叉树

问题描述

创建两棵由非负整型值组成的二叉树 tree0 和 tree1,求出这两棵二叉树的高度及规模(节点数)并将他们归并为一棵新的树 tree2,求新的二叉树的高度、规模以及该树的前序、中序和后序遍历结果。

输入

第一行输入 tree0 的数据;
第二行输入 tree1 的数据;
第三行输入 tree2 根节点的数据

  • 备注:
    二叉树的创建过程为先输入根结点的值,创建根节点;对已添加到树上的每个结点,依次输入它的两个儿子的值。如果没有儿子,则输入特定值-1 作为标志。循环上述操作直至二叉树创建完成,下图为一个二叉树创建实例:
  • 二叉树的归并过程为将第三行输入的数据作为 tree2 的根节点,tree0 根节点成为 tree2 根节点的左节点,tree1 根节点成为 tree2根节点的右节点。
输出

第一行输出 tree0 的高度与规模;
第二行输出 tree1 的高度与规模;
第三行输出 tree2 的高度与规模;
第四行输出 tree2 的前序遍历结果;
第五行输出 tree2 的中序遍历结果;
第六行输出 tree2 的后序遍历结果

  • 备注:
    注意输出最后一行数据后即结束程序运行,不做换行处理。
样例

输入

1 2 3 4 -1 5 6 -1 -1 7 -1 -1 -1 -1 -1
3 2 1 0 -1 -1 -1 0 -1 0 -1 -1 -1
10

输出

4 7
5 6
6 14
10 1 2 4 3 5 7 6 3 2 0 0 0 1
4 2 1 7 5 3 6 10 0 0 0 2 3 1
4 2 7 5 6 3 1 0 0 0 2 1 3 10

解题思路

该问题创建两棵二叉树,求出树的高度和规模并且归并两棵树,输出归并后的前序、中序和后序遍历结果。由于树的特性,我们可以采用递归算法来求解树的高度、规模以及三种遍历结果,归并树时只需创建一个新节点,该结点的左结点和右结点分别指向两棵树的根节点即可。

代码实现

结点类(Node)

首先是树的结点设计,可以设计出下图所示结构体,Node 表示结点,其中包含有数据域data和左指针域left以及右指针域right,用于指向孩子结点。


其中 data 表示数据,其可以是简单的类型,也可以是复杂的结构体,故采用泛型编程template<typename Type>。左右指针表示,左右孩子,其指向下一个结点。结点类还有构造函数,在创建结点时可以进行初始化。

template <class Type>
struct Node {
        Node* left;
        Node* right;
        Type data;

        Node() :left(NULL), right(NULL) {}
        Node(Type item, Node* L = NULL, Node* R = NULL) :data(item), left(L), right(R) {}

        ~Node() {}
    };
二叉树类(Binary Tree)

二叉树类(Binary Tree)采用泛型编程,其中Type是模板参数,代表树中元素的类型。root为树的根结点,利用根结点可对整个树进行增删查改操作。

公有函数

含有一些基本函数或者通过公有函数调用私有函数,完成查询树根节点、高度和规模等功能

构造函数

构造函数用于初始化根节点,将root初始化为 NULL,表示树为空。

基本函数

getRoot() : 返回根节点的值

getLeft(): 返回左子节点的值

getRight() : 返回右子节点的值

Node* getroot() : 返回指向根节点的指针

int size() const { return size(root); }: 返回二叉树的节点数(规模)

int height() const { return height(root); }: 返回二叉树的高度。

preOrder(),midOrder(),postOrder():用于调用私有的前序、中序、后序遍历函数

template <class Type>
class BinaryTree {
public:
    BinaryTree() :root(NULL) {}
    BinaryTree(const Type& value) {
        root = new Node(value);
    }

    Type getRoot() const { return root->data; }
    Type getLeft() const { return root->left->data; }
    Type getRight() const { return root->right->data; }
    Node* getroot() { return root; }
    int size() const { return size(root); }
    int height() const { return height(root); }
    }
     void preOrder() const {
        if (root != NULL) {
            preOrder(root);
        }
        cout << endl;
    }

    void midOrder() const {
        if (root != NULL) {
            midOrder(root);
        }
        cout << endl;
    }

    void postOrder() const {
        if (root != NULL) {
            postOrder(root);
        }
        //cout << endl;
    }
};
createTree()函数 

用于创建一个二叉树,并根据输入的数据构建该二叉树的结构。

该函数首先定义一个队列 que,用于辅助构建二叉树,从标准输入中读取第一个节点的值 x,并检查其是否为标记值,如果是则将根节点置为空,结束函数。

如果根节点值不是标记值,则创建根节点,并将其加入队列中。

使用队列进行层次遍历:从队列中取出一个节点 tmp,并移出队列。 从标准输入中读取该节点的左子节点值 ldata 和右子节点值 rdata。 如果左子节点值不是标记值,则创建一个新节点,并将其设为当前节点 tmp 的左子节点,同时将新节点加入队列。 如果右子节点值不是标记值,则创建一个新节点,并将其设为当前节点 tmp 的右子节点,同时将新节点加入队列。 循环直到队列为空,即所有节点都已经处理完毕

void createTree(float flag) {
        queue<Node*> que;
        Node* tmp;
        Type x, ldata, rdata;
        cin >> x;
        if (x == flag) {
            root = NULL;
            return;
        }
        root = new Node(x);
        que.push(root);
        while (!que.empty())
        {
            tmp = que.front();
            que.pop();
            cin >> ldata >> rdata;
            if (ldata != flag)
                que.push(tmp->left = new Node(ldata));
            if (rdata != flag)
                que.push(tmp->right = new Node(rdata));
        }
    }
height()函数

递归求树高,如果当前节点不为空,则递归计算左子树和右子树的高度,并取较大值,返回 1(当前节点的高度)加上左右子树中较大的高度

int height(Node* t) const { 
        if (t == NULL) return 0;
        else {
            int lt = height(t->left), rt = height(t->right);
            return 1 + ((lt > rt) ? lt : rt);
        }
        }
size()函数 

递归求节点数(规模),如果当前节点不为空,则递归计算左子树和右子树的规模,并相加,返回 1(当前节点的规模)加上左右子树的节点数

int size(Node * t) const {
            if (t == NULL) return 0;
            return 1 + size(t->left) + size(t->right);
        }
遍历函数
preOrder()函数

对给定二叉树进行前序遍历,并将遍历结果输出到标准输出流中。如果当前节点指针不为空,则先输出当前节点的值,然后递归地对左子树和右子树进行前序遍历

midOrder 函数

对给定二叉树进行中序遍历,并将遍历结果输出到标准输出流中。如果当前节点指针不为空,则先递归地对左子树进行中序遍历,然后输出当前节点的值,最后递归地对右子树进行中序遍历。

postOrder 函数 

对给定二叉树进行后序遍历,并将遍历结果输出到标准输出流中。 如果当前节点指针不为空,则先递归地对左子树进行后序遍历,然后递归地对右子树进行后序遍历,最后输出当前节点的值。

void preOrder(Node * t) const {
            if (t != NULL) {
                cout << t->data << ' ';
                preOrder(t->left);
                preOrder(t->right);
            }
        }
        void midOrder(Node * t) const {
            if (t != NULL) {
                midOrder(t->left);
                cout << t->data << ' ';
                midOrder(t->right);
            }
        }
        void postOrder(Node * t) const {
            if (t != NULL) {
                postOrder(t->left);
                postOrder(t->right);
                cout << t->data << ' ';
            }
        }
makeTree()函数

创建一棵新的二叉树,并将两棵已有的二叉树作为其左子树和右子树

创建一个新的根节点,左节点指向tree1的根结点,右节点指向tree2的根结点

void makeTree(const Type& x, BinaryTree& lt, BinaryTree& rt) {
        root = new Node(x);
        root->left = lt.getroot();
        root->right = rt.getroot();
    }
clear()函数

用于清空树的所有节点

如果输入的根节点指针 t 为 NULL,则表示当前节点为空,直接返回,不做任何操作。 递归清空左子树和右子树,即先清空左子树,再清空右子树。 在清空左右子树后,删除当前节点 t,释放其内存

void clear(Node * t) {
         if (t->left != NULL)
                clear(t->left);
         if (t->right != NULL)
                clear(t->right);
            delete t;
            //t = NULL;
            return;
        }
完整代码(递归法)
#include <queue>
#include <stack>
#include <iostream>
using namespace std;
template <class Type>
class BinaryTree {
private:
    struct Node {
        Node* left;
        Node* right;
        Type data;

        Node() :left(NULL), right(NULL) {}
        Node(Type item, Node* L = NULL, Node* R = NULL) :data(item), left(L), right(R) {}

        ~Node() {}
    };
    Node* root;

public:
    BinaryTree() :root(NULL) {}
    BinaryTree(const Type& value) {
        root = new Node(value);
    }

    Type getRoot() const { return root->data; }
    Type getLeft() const { return root->left->data; }
    Type getRight() const { return root->right->data; }
    Node* getroot() { return root; }
    void makeTree(const Type& x, BinaryTree& lt, BinaryTree& rt) {
        root = new Node(x);
        root->left = lt.getroot();
        root->right = rt.getroot();
    }
    int size() const { return size(root); }
    int height() const { return height(root); }

    void preOrder() const {
        if (root != NULL) {
            preOrder(root);
        }
        cout << endl;
    }

    void midOrder() const {
        if (root != NULL) {
            midOrder(root);
        }
        cout << endl;
    }

    void postOrder() const {
        if (root != NULL) {
            postOrder(root);
        }
        //cout << endl;
    }
    // 此处输入等于 flag 表示该位置没有节点的情况
    void createTree(float flag) {
        queue<Node*> que;
        Node* tmp;
        Type x, ldata, rdata;
        cin >> x;
        if (x == flag) {
            root = NULL;
            return;
        }
        root = new Node(x);
        que.push(root);
        while (!que.empty())
        {
            tmp = que.front();
            que.pop();
            cin >> ldata >> rdata;
            if (ldata != flag)
                que.push(tmp->left = new Node(ldata));
            if (rdata != flag)
                que.push(tmp->right = new Node(rdata));
        }
    }

private:
    int height(Node* t) const { if (t == NULL) return 0;
        else {
            int lt = height(t->left), rt = height(t->right);
            return 1 + ((lt > rt) ? lt : rt);
        }
        }
    void clear(Node * t) {
         if (t->left != NULL)
                clear(t->left);
         if (t->right != NULL)
                clear(t->right);
            delete t;
            //t = NULL;
            return;
        }
        int size(Node * t) const {
            if (t == NULL) return 0;
            return 1 + size(t->left) + size(t->right);
        }
        //递归实现
        void preOrder(Node * t) const {
            if (t != NULL) {
                cout << t->data << ' ';
                preOrder(t->left);
                preOrder(t->right);
            }
        }
        //递归实现
        void midOrder(Node * t) const {
            if (t != NULL) {
                midOrder(t->left);
                cout << t->data << ' ';
                midOrder(t->right);
            }
        }
        //递归实现
        void postOrder(Node * t) const {
            if (t != NULL) {
                postOrder(t->left);
                postOrder(t->right);
                cout << t->data << ' ';
            }
        }
    };

int main() {
    int x;
    BinaryTree<int> tree, tree1, tree2;
    //第一颗树的构建,-1 作为没有子节点的标志
    tree.createTree(-1);
    cout << tree.height() << ' ' << tree.size() << endl;

    //第二颗树的构建
    tree1.createTree(-1);
    cout << tree1.height() << ' ' << tree1.size() << endl;

    //合并两棵树,x 为根节点的值
    cin >> x;
    tree2.makeTree(x, tree, tree1);
    cout << tree2.height() << ' ' << tree2.size() << endl;
    tree2.preOrder();
    tree2.midOrder();
    tree2.postOrder();
    return 0;
}

递归算法的问题

 (1) 存在不支持递归算法的程序设计语言  

 (2) 递归算法在运行中,需要系统在内存栈中分配空间保存函数的参数、返回地址以及局部变量等,运行效率较低    

(3)系统对每个进程分配的栈容量有限,如果二叉树的深度太大造成递归调用的层次太高,容易导致栈溢出

下面我们采用非递归算法实现二叉树,即用迭代的方法实现二叉树,关键是使用栈结构模拟函数调用中系统栈的工作原理

二叉树遍历迭代器

问题描述

二叉树的遍历可以使用递归算法实现, 但是递归算法的执行速度较慢, 而非递归的实现比较复杂。 现在希望使用二叉树的迭代器来实现二叉树的三种遍历(即, 前序遍历、 中序遍历和后序遍历),三种遍历对应于三种迭代器。

输入

第一行输入二叉树的节点数据, 用来创建一棵二叉树, 其输入逻辑类同实验“二叉树类的应用”。

  • 备注:
    1)二叉树的创建过程为先输入根结点的值, 创建根节点;
    2)对已添加到树上的每个结点, 依次输入它的两个儿子的值。 3)如果没有儿子, 则输入特定值-1 作为标志。
    循环上述操作直至二叉树创建完成,下图为一个二叉树创建实例:
输出

输出一共三行, 分别表示前、 中、 后序遍历结果, 每行整数间使用空格分开。

  • 备注:
    输出最后一行数据后即结束程序运行,不做换行处理。
样例

输入:

1 2 3 -1 -1 4 -1 -1 -1

输出:

1 2 3 4
2 1 4 3
2 4 3 1

解题思路

使用二叉树的迭代器来实现二叉树的三种遍历(前序遍历、中序遍历和后序遍历)可以通过栈来实现。

前序遍历迭代器

创建一个栈,将根节点压入栈中。 循环直到栈为空

(1)弹出栈顶节点,并输出其值

(2)如果栈顶节点有右子节点,则将右子节点压入栈中

(3)如果栈顶节点有左子节点,则将左子节点压入栈中

中序遍历迭代器

创建一个栈,将根节点及其所有左子节点压入栈中, 循环直到栈为空

(1)弹出栈顶节点,并输出其值

(2)如果弹出的节点有右子节点,则将右子节点及其所有左子节点压入栈中

后序遍历迭代器

创建两个栈,一个用于存储遍历结果,另一个用于辅助遍历。 将根节点压入第一个栈中。 循环直到第一个栈为空

(1)弹出栈顶节点,将其值插入第二个栈的栈顶

(2)如果弹出的节点有左子节点,则将左子节点压入第一个栈中

(3)如果弹出的节点有右子节点,则将右子节点压入第一个栈中

(4)最后输出第二个栈的所有元素即为后序遍历结果。

代码实现

树结点类(Node)

和递归方法的树结点基本相同,不再赘述

struct Node {
    Node* left;
    Node* right;
    Type data;
    Node() :left(NULL), right(NULL) {}
    Node(Type item, Node* L = NULL, Node* R = NULL) :data(item), left(L), right(R) {}
}; 
辅助结点类(StNode)

StNode 是用来辅助实现后序遍历的迭代器中的结构体。
(1)StNode 结构体存储了二叉树节点的指针信息以及辅助计数信息
(2)TimesPop 成员变量用于记录节点被弹出栈的次数
(3)StNode 结构体主要用于后序遍历迭代器中,通过记录节点被弹出栈的次数,可以判断何时访问节点,并确定节点的访问顺序。

template <class Type>
struct StNode
{
    Node<Type>* node;
    int TimesPop;
    StNode(Node<Type>* N = NULL) : node(N), TimesPop(0) {}
};
链式栈类(linkStack)

该链式栈类实现了栈的基本功能,包括入栈、出栈、返回栈顶元素、判断栈是否为空以及清空栈。由于采用链表作为底层数据结构,可以动态地管理内存空间

栈的详解可阅读:手把手教数据结构与算法:栈的应用(平衡符号和简单计算器)_计算器系统 栈-CSDN博客

template <class elemType>
class linkStack : public stack<elemType>
{
private:
    struct node {
        elemType data;
        node* next;
        node(const elemType& x, node* N = NULL) { data = x; next = N; }
        node() :next(NULL) {}
        ~node() {}
    };
    //结点定义 next和数据
    node* elem;
public:
    linkStack() { elem = NULL; }
    ~linkStack() {
        node* tmp;
        while (elem != NULL) {
            tmp = elem;
            elem = elem->next;
            delete tmp;
        }
    }
    bool isEmpty() { return elem == NULL; }
    void push(const elemType& x) {
        node* tmp = new node(x, elem);
        elem = tmp;
    }
    // 入栈
    elemType pop() {
        node* tmp = elem;
        elemType x = tmp->data;
        elem = elem->next;
        delete tmp;
        return x;
    }
    // 出栈 并返回头结点数据
    elemType top() { return elem->data; } 
    void makeEmpty() {
        node* tmp;
        while (elem != NULL) {
            tmp = elem;
            elem = elem->next;
            delete tmp;
        }
        elem = NULL;
    }
    // 清空栈
};
 TreeIterator类

TreeIterator 类提供一个通用的接口,用于实现二叉树的迭代器功能。通过派生类实现不同的遍历方式的 First() 和 operator++() 函数,可以实现前序、中序和后序遍历等不同的遍历方式。

template <class Type>
class TreeIterator {
public:
    TreeIterator(const BinaryTree<Type>& BT) : T(BT), current(NULL) {}
    virtual ~TreeIterator() {}
    // 第一个被访问的结点地址送 current
    virtual void First() = 0;
    // 下一个被访问的结点地址送 current
    virtual void operator++() = 0;
    // 判当前结点为空吗, 为空返回 True
    bool operator+() const { return current != NULL; }
    // 返回当前结点指针 current 所指向的结点的数据值。
    Type& operator()() const { return current->data; }

public:
    const BinaryTree<Type>& T;
    // BinaryTree<Type>::Node* current;
    Node<Type>* current;
    // 指向当前结点的指针。
};
前序遍历(Preorder) 

成员函数 First重写了基类中的纯虚函数 First(),用于定位到第一个被访问的节点。在该函数中,首先清空栈 s,然后将二叉树的根节点压入栈 s 中,最后调用 operator++() 函数。

成员函数 operator++

重写了基类中的纯虚函数 operator++(),用于定位到下一个被访问的节点。

(1)在该函数中,首先判断栈 s 是否为空,如果为空则将当前节点指针 current 置为空并返回;(2)如果栈不为空,则弹出栈顶节点,并将其指定为当前节点。然后,如果当前节点有右子节点,则将右子节点压入栈 s 中;如果当前节点有左子节点,则将左子节点压入栈 s 中。

template <class Type>
class Preorder : public TreeIterator<Type> //前序遍历迭代器
{
public:
    Preorder(const BinaryTree<Type>& R) : TreeIterator<Type>(R) { s.push(this->T.root); }
    ~Preorder() {}

    void First() {
        s.makeEmpty();
        if (this->T.root) s.push(this->T.root);
        operator++();
    }
    void operator++() {
        if (s.isEmpty()) {
            this->current = NULL; return;
        }
        this->current = s.top();
        s.pop();
        // 得到当前结点的地址, 并进行出栈操作。
        if (this->current->right != NULL)
            s.push(this->current->right); //非空右儿子进栈。
        if (this->current->left != NULL)
            s.push(this->current->left);  //非空左儿子进栈。
    }
protected:
    linkStack<Node<Type>*> s;
};
中序遍历(InOrder)

(1)首先,检查栈 s 是否为空,如果为空,说明已经完成了中序遍历,将当前节点指针 current 置为 NULL 并返回。

(2)如果栈不为空,则从栈 s 中弹出一个节点 Cnode。 对弹出的节点 Cnode 进行操作:

如果节点 Cnode 的遍历状态计数器 TimesPop 的值为 2,表示左子树已经被处理,当前节点可以被访问:将当前节点指针 current 指向节点 Cnode。 如果节点 Cnode 存在右子节点,则将右子节点压入栈 s 中,为下一次迭代做准备。 返回,结束函数执行。

如果节点 Cnode 的遍历状态计数器 TimesPop 的值不为 2,表示左子树还未处理,需要继续处理:将节点 Cnode 再次压入栈 s 中,表示暂时不访问当前节点,继续处理其左子树。 如果节点 Cnode 存在左子节点,则将其左子节点压入栈 s 中,为下一次迭代做准备。

template <class Type> class Inorder : public Postorder < Type >
{
public:
    Inorder(const BinaryTree < Type >& R) : Postorder<Type>(R) { }
    void operator++(); // 中序时的下一个结点的地址。
};
template <class Type>
void Inorder<Type>::operator++()
{
    if (this->s.isEmpty())
    { // 当栈空且 current 也为空时, 遍历结束。
        this->current = NULL; return;
    } // 置当前指针为空, 结束。
    StNode<Type> Cnode;
    for (; ; )
    {
            Cnode = this->s.top(); this->s.pop();
            if (++Cnode.TimesPop == 2)  // 左子树已处理,该结点可访问。
            {
                this->current = Cnode.node;
                if (Cnode.node->right != NULL)   // 有右儿子,进栈。
                    this->s.push(StNode<Type>(Cnode.node->right));
                return;
            }
            this->s.push(Cnode);
            if (Cnode.node->left != NULL)	
                this->s.push(StNode<Type>(Cnode.node->left));
    }
}
后序遍历(PostOrder)

(1)检查栈是否为空: 如果栈 s 为空,则表示遍历已经结束,将当前节点指针 current 置为空,并返回。

(2)弹出栈顶节点: 从栈 s 中弹出一个节点 Cnode。 更新节点的遍历状态: 对 Cnode 的遍历状态计数器 TimesPop 进行增加操作,表示当前节点被访问的次数。

(3)判断节点是否可访问: 如果 Cnode 的遍历状态计数器为 3,则表示当前节点已经完成了左右子树的访问,可以被访问。将当前节点指针 current 指向 Cnode.node,即当前节点。 返回,结束函数执行。

(4)根据节点的遍历状态进行入栈操作: 如果 Cnode 的遍历状态计数器为 1,则表示当前节点的左子树还未访问,需要访问左子树。如果当前节点有左子节点,则将其压入栈 s 中,同时将遍历状态计数器设为 2,表示左子树访问结束。 如果 Cnode 的遍历状态计数器为 2,则表示当前节点的左子树已经访问完毕,需要访问右子树。如果当前节点有右子节点,则将其压入栈 s 中,同时将遍历状态计数器设为 3,表示右子树访问结束。

template <class Type>
class Postorder : public TreeIterator<Type>
{
public:
    Postorder(const BinaryTree < Type >& R) : TreeIterator<Type>(R) {
        s.push(StNode<Type>(this->T.root));
    }
    ~Postorder() { }
    // 后序遍历时的第一个结点的地址。
    void First() {
        s.makeEmpty();
        if (this->T.root)  s.push(this->T.root);
        operator++();
    }
    // 后序遍历时的下一个结点的地址。
    void operator++() {
        if (s.isEmpty())
        { // 当栈空且 current 也为空时, 遍历结束。
            this->current = NULL;
            return;
        } // 置当前指针为空,结束。
        StNode<Type> Cnode;
        for (; ; )
        {
            { Cnode = s.top(); s.pop();
            if (++Cnode.TimesPop == 3) //该结点可访问。
            {
                this->current = Cnode.node; return;
            }
            s.push(Cnode);
            if (Cnode.TimesPop == 1)   // 转向访问左子树。 
            {
                if (Cnode.node->left != NULL)   // 有左儿子,进栈。
                    s.push(StNode<Type>(Cnode.node->left));
            }
            else {// Cnode.TimesPop == 2 访左结束,转向访问右子树。
                if (Cnode.node->right != NULL)
                    s.push(StNode<Type>(Cnode.node->right));
            }
            }
        }
    }
protected:
    linkStack<StNode<Type>> s;
};
完整代码(迭代器)
#include <queue>
#include <stack>
#include <iostream>
using namespace std;
template <typename Type>
struct Node {
    Node* left;
    Node* right;
    Type data;
    Node() :left(NULL), right(NULL) {}
    Node(Type item, Node* L = NULL, Node* R = NULL) :data(item), left(L), right(R) {}
}; 
//结点定义 左右子和数据
template <class Type>
struct StNode
{
    Node<Type>* node;
    int TimesPop;
    StNode(Node<Type>* N = NULL) : node(N), TimesPop(0) {}
};
template <class elemType>
class linkStack : public stack<elemType>
{
private:
    struct node {
        elemType data;
        node* next;
        node(const elemType& x, node* N = NULL) { data = x; next = N; }
        node() :next(NULL) {}
        ~node() {}
    };
    //结点定义 next和数据
    node* elem;
public:
    linkStack() { elem = NULL; }
    ~linkStack() {
        node* tmp;
        while (elem != NULL) {
            tmp = elem;
            elem = elem->next;
            delete tmp;
        }
    }
    bool isEmpty() { return elem == NULL; }
    void push(const elemType& x) {
        node* tmp = new node(x, elem);
        elem = tmp;
    }
    // 入栈
    elemType pop() {
        node* tmp = elem;
        elemType x = tmp->data;
        elem = elem->next;
        delete tmp;
        return x;
    }
    // 出栈 并返回头结点数据
    elemType top() { return elem->data; } 
    void makeEmpty() {
        node* tmp;
        while (elem != NULL) {
            tmp = elem;
            elem = elem->next;
            delete tmp;
        }
        elem = NULL;
    }
    // 清空栈
};
template <class Type>
class BinaryTree {
public:
    Node<Type>* root;
    struct stNode {
        Node<Type>* node;
        int timesPop;
        stNode(Node<Type>* N = NULL) :node(N), timesPop(0) {}
    };
public:
    BinaryTree() :root(NULL) {}
    BinaryTree(const Type& value) {
        root = new Node<Type>(value);
    }
    ~BinaryTree() {
        clear();
    }
    void clear() {
        if (root != NULL)
            clear(root);
        root = NULL;
    }
    void createTree(Type flag) {
        queue<Node<Type>*> que;
        Node<Type>* tmp;
        Type x, ldata, rdata;
        cin >> x;
        root = new Node<Type>(x);
        que.push(root);
        while (!que.empty())
        {
            tmp = que.front();
            que.pop();
            cin >> ldata >> rdata;
            if (ldata != flag)
                que.push(tmp->left = new Node<Type>(ldata));
            if (rdata != flag)
                que.push(tmp->right = new Node<Type>(rdata));
        }
    }
    // 创建一个树 并设置标志
private:
    void clear(Node<Type>* t) {
        if (t->left != NULL)
            clear(t->left);
        if (t->right != NULL)
            clear(t->right);
        delete t;
        return;
    }
    // 清除结点t
};
template <class Type>
class TreeIterator {
public:
    TreeIterator(const BinaryTree<Type>& BT) : T(BT), current(NULL) {}
    virtual ~TreeIterator() {}
    // 第一个被访问的结点地址送 current
    virtual void First() = 0;
    // 下一个被访问的结点地址送 current
    virtual void operator++() = 0;
    // 判当前结点为空吗, 为空返回 True
    bool operator+() const { return current != NULL; }
    // 返回当前结点指针 current 所指向的结点的数据值。
    Type& operator()() const { return current->data; }

public:
    const BinaryTree<Type>& T;
    // BinaryTree<Type>::Node* current;
    Node<Type>* current;
    // 指向当前结点的指针。
};
template <class Type>
class Preorder : public TreeIterator<Type> //前序遍历迭代器
{
public:
    Preorder(const BinaryTree<Type>& R) : TreeIterator<Type>(R) { s.push(this->T.root); }
    ~Preorder() {}

    void First() {
        s.makeEmpty();
        if (this->T.root) s.push(this->T.root);
        operator++();
    }
    void operator++() {
        if (s.isEmpty()) {
            this->current = NULL; return;
        }
        this->current = s.top();
        s.pop();
        // 得到当前结点的地址, 并进行出栈操作。
        if (this->current->right != NULL)
            s.push(this->current->right); //非空右儿子进栈。
        if (this->current->left != NULL)
            s.push(this->current->left);  //非空左儿子进栈。
    }
protected:
    linkStack<Node<Type>*> s;
};

template <class Type>
class Postorder : public TreeIterator<Type>
{
public:
    Postorder(const BinaryTree < Type >& R) : TreeIterator<Type>(R) {
        s.push(StNode<Type>(this->T.root));
    }
    ~Postorder() { }
    // 后序遍历时的第一个结点的地址。
    void First() {
        s.makeEmpty();
        if (this->T.root)  s.push(this->T.root);
        operator++();
    }
    // 后序遍历时的下一个结点的地址。
    void operator++() {
        if (s.isEmpty())
        { // 当栈空且 current 也为空时, 遍历结束。
            this->current = NULL;
            return;
        } // 置当前指针为空,结束。
        StNode<Type> Cnode;
        for (; ; )
        {
            { Cnode = s.top(); s.pop();
            if (++Cnode.TimesPop == 3) //该结点可访问。
            {
                this->current = Cnode.node; return;
            }
            s.push(Cnode);
            if (Cnode.TimesPop == 1)   // 转向访问左子树。 
            {
                if (Cnode.node->left != NULL)   // 有左儿子,进栈。
                    s.push(StNode<Type>(Cnode.node->left));
            }
            else {// Cnode.TimesPop == 2 访左结束,转向访问右子树。
                if (Cnode.node->right != NULL)
                    s.push(StNode<Type>(Cnode.node->right));
            }
            }
        }
    }
protected:
    linkStack<StNode<Type>> s;
};
template <class Type> class Inorder : public Postorder < Type >
{
public:
    Inorder(const BinaryTree < Type >& R) : Postorder<Type>(R) { }
    void operator++(); // 中序时的下一个结点的地址。
};
template <class Type>
void Inorder<Type>::operator++()
{
    if (this->s.isEmpty())
    { // 当栈空且 current 也为空时, 遍历结束。
        this->current = NULL; return;
    } // 置当前指针为空, 结束。
    StNode<Type> Cnode;
    for (; ; )
    {
            Cnode = this->s.top(); this->s.pop();
            if (++Cnode.TimesPop == 2)  // 左子树已处理,该结点可访问。
            {
                this->current = Cnode.node;
                if (Cnode.node->right != NULL)   // 有右儿子,进栈。
                    this->s.push(StNode<Type>(Cnode.node->right));
                return;
            }
            this->s.push(Cnode);
            if (Cnode.node->left != NULL)	
                this->s.push(StNode<Type>(Cnode.node->left));
    }
}
int main()
{
    BinaryTree<int> tree;
    tree.createTree(-1);

    Preorder<int> pre(tree);
    for (pre.First(); +pre; ++pre)
        cout << pre() << ' ';
    cout << endl;

    Inorder<int> in(tree);
    for (in.First(); +in; ++in)
        cout << in() << ' ';
    cout << endl;

    Postorder<int> post(tree);
    for (post.First(); +post; ++post)
        cout << post() << ' ';
    return 0;
}

附录

分类专栏

链接:

​​​​​手把手教数据结构与算法

本专栏上一节

链接:

手把手教数据结构与算法:优先级队列(银行排队问题)-CSDN博客

  • 151
    点赞
  • 128
    收藏
    觉得还不错? 一键收藏
  • 177
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 177
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值