c++ 二叉树实现,遍历及重构

这几天在搞树和图,转自邓俊辉老师的数据结构。

二叉树的实现

主要还是树节点的实现。方法用来实现功能的可以先不看,主要是节点的属性,左孩子,右孩子,父节点,数据。

树节点BinNode

#define BinNodePosi(T) BinNode<T>*
template<typename T>
struct BinNode {
    BinNodePosi(T) parent;
    BinNodePosi(T)lChild; 
    BinNodePosi(T) rChild;//父亲、孩子
    T data; int height;
    int size();//子树规模
    BinNodePosi(T) insertAsLC(T const& e);//作为左孩子插入新节点
    BinNodePosi(T) insertAsRC(T const& e);//作为右孩子插入新节点
    BinNodePosi(T) succ();//中序遍历意义下,返回当前节点的直接后继

    BinNode() {};
    BinNode(T d, BinNodePosi(T) p = nullptr) :data(d), parent(p),lChild(nullptr),rChild(nullptr) {};

    //遍历的辅助函数
    //沿着左分支一直向下,并访问
    static void visitAlongLeftBranch(BinNodePosi(T) x, Stack<BinNodePosi(T)>& stack);
    //沿着左分支一直向下,不访问
    static void goAlongLeftBranch(BinNodePosi(T) x, Stack<BinNodePosi(T)>& stack);
    //沿着左分支一直向下,并将右孩子放入栈
    static void goToLeftMostBranch(Stack<BinNodePosi(T)>& stack);

    static void travLevel(BinNodePosi(T) x);//层次遍历
    static void travPre1(BinNodePosi(T) x);//先序遍历

    static void travPre2(BinNodePosi(T) x);
    
    static void travIn(BinNodePosi(T) x);//中序遍历
    static void travPost(BinNodePosi(T) x);//后序遍历

    static void travRecursion(BinNodePosi(T) x, int index);

    static BinNodePosi(T) getNodePostIn(Vector<T> inOrder, Vector<T> postOrder);
    static BinNodePosi(T) getNodePreIn(Vector<T> inOrder, Vector<T> preOrder);
};

template<typename T>
BinNodePosi(T) BinNode<T>::insertAsLC(T const& e) {
    return lChild = new BinNode(e, this);
};

template<typename T>
BinNodePosi(T) BinNode<T>::insertAsRC(T const& e) {
    return rChild = new BinNode(e, this);
};

template<typename T>
int BinNode<T>::size() {
    int s = 1;
    if (lChild) s += lChild->size();
    if (rChild) s += rChild->size();
    return s;
}

树的实现,用来储存根节点,封装节点的方法,主要实现还是在节点那里。

#include"BinNode.h"

#define stature(p) ((p)?p->height:-1)

template<typename T>
class BinTree {
protected:
    int _size;//规模
    BinNodePosi(T) _root;//根节点
public:
    BinTree() { _size = 0; _root = nullptr; }
    ~BinTree();

    int updateHeight(BinNodePosi(T) x);//更新节点高度
    void updateHeightAbove(BinNodePosi(T) x);//更新x及祖先的高度

    int size() const { return _size; }//规模
    bool empty() const { return !_root; }//判空
    BinNodePosi(T) root() const { return _root; }//树根

    void setRoot(BinNodePosi(T) x) { _root = x; }

    BinNodePosi(T) insertAsLC(BinNodePosi(T) x, T const& e);//为x插入左孩子
    BinNodePosi(T) insertAsRC(BinNodePosi(T) x, T const& e);//为x插入右孩子
};

template<typename T>
int BinTree<T>::updateHeight(BinNodePosi(T) x) {
    return x->height = 1 +
        (stature(x->lChild) > stature(x->rChild) ? stature(x->lChild) : stature(x->rChild));
};

template<typename T>
void BinTree<T>::updateHeightAbove(BinNodePosi(T) x) {
    while (x) {
        updateHeight(x);
        x = x->parent;
    }
};

template<typename T>
BinNodePosi(T) BinTree<T>::insertAsRC(BinNodePosi(T) x, T const& e) {
    _size++;
    x->insertAsRC(e);
    updateHeightAbove(x);
    return x->rChild;

};
template<typename T>
BinNodePosi(T) BinTree<T>::insertAsLC(BinNodePosi(T) x, T const& e) {
    _size++;
    x->insertAsLC(e);
    updateHeightAbove(x);
    return x->lChild;
};

template<typename T>
BinTree<T>::~BinTree() {
    Queue<BinNodePosi(T)> queue;
    if (_root) queue.enqueue(_root);
    while (!queue.empty()) {
        BinNodePosi(T) tempNode = queue.dequeue();
        if (tempNode->lChild) 
            queue.enqueue(tempNode->lChild);
        if (tempNode->rChild) 
            queue.enqueue(tempNode->rChild);
        delete tempNode;
    }
    _root = nullptr;

};

需要注意的是updateHeight,对空节点(nullptr)的高度定义为-1,节点高度定义为到最远的叶节点的距离,有点递归的味道。

二叉树的析构

相当与一个层次遍历,原理在下面,一定要注意,差点忘了。

不能用先序,中序之类的,因为释放了一个节点,可能他的孩子还没有被释放,这样孩子就没有引用指向他了,相当于这个节点的孩子没被删掉。

template<typename T>
BinTree<T>::~BinTree() {
    Queue<BinNodePosi(T)> queue;
    if (_root) queue.enqueue(_root);
    while (!queue.empty()) {
        BinNodePosi(T) tempNode = queue.dequeue();
        if (tempNode->lChild) 
            queue.enqueue(tempNode->lChild);
        if (tempNode->rChild) 
            queue.enqueue(tempNode->rChild);
        delete tempNode;
    }
    _root = nullptr;

};

遍历

先清楚定义

层次遍历:越靠上的,越靠左的,越先输出

前序遍历:父节点 左孩子 右孩子

中序遍历:左孩子 父节点 右孩子

后序遍历:左孩子 右孩子 父节点

层次遍历

利用队列先进后出的原则,我们实现层次遍历。

思路:

  1. 从queue中取出队头,访问,要是他有左孩子,入队,要是他有右孩子,也入队

  1. 循环,直至queue为空

对于队列中的任意一个节点x,后面的先是他的右兄弟,同一层次的右边的兄弟,然后是下一层次的节点。

template<typename T>
void BinNode<T>::travLevel(BinNodePosi(T) x) {
    Queue<BinNodePosi(T)> queue;
    queue.enqueue(x);
    while (!queue.empty()) {
        BinNodePosi(T) temp = queue.dequeue();
        cout << temp->data << " ";
        if (temp->lChild) queue.enqueue(temp->lChild);
        if (temp->rChild) queue.enqueue(temp->rChild);
    }

};

递归实现

从定义出发,写的递归顺序,一目了然。

template<typename T>
void BinNode<T>::travRecursion(BinNodePosi(T) x, int index) {
    if (index == 1) {
        //前序遍历
        if (!x) return;
        cout << x->data << " ";
        travRecursion(x->lChild, index);
        travRecursion(x->rChild, index);
    }
    else if (index == 2) {
        //中序遍历
        if (!x) return;
        travRecursion(x->lChild, index);
        cout << x->data << " ";
        travRecursion(x->rChild, index);
    }
    else if (index == 3) {
        //后序遍历
        if (!x) return;
        travRecursion(x->lChild, index);
        travRecursion(x->rChild, index);
        cout << x->data << " ";
    }

};

前序遍历迭代实现

版本一:

思路:

前序遍历的顺序是----父节点,左孩子,右孩子。

  1. 从stack中取出一个节点,直接访问

  1. 如果有右孩子,先存入stack

  1. 如果有左孩子,后存入stack

  1. 循环,直至stack中的size为空

template<typename T>
void BinNode<T>::travLevel(BinNodePosi(T) x) {
    Queue<BinNodePosi(T)> queue;
    queue.enqueue(x);
    while (!queue.empty()) {
        BinNodePosi(T) temp = queue.dequeue();
        cout << temp->data << " ";
        if (temp->lChild) queue.enqueue(temp->lChild);
        if (temp->rChild) queue.enqueue(temp->rChild);
    }

};

template<typename T>
void BinNode<T>::travPre1(BinNodePosi(T) x) {
    Stack<BinNodePosi(T)> stack;
    stack.push(x);
    while (!stack.empty()) {
        BinNodePosi(T) temp = stack.pop();
        cout << temp->data << " ";
        if (temp->rChild) stack.push(temp->rChild);
        if (temp->lChild) stack.push(temp->lChild);
    }
};

stack储存,不适合用同样的方式实现中序和后序遍历。

版本二

思路

  1. 沿着根节点向左,碰到一个节点访问一个节点,同时,他要是有右孩子,直接存入stack中

  1. 到叶节点,从stack中取出一个节点

  1. 沿着新节点向左,循环,至stack的size为0

版本二比版本一好,版本一中每一个节点要想访问,必需在stack中走一遍,而版本二中,并不是所有节点都会进入到stack中,而是作为右孩子的节点。而且版本二可以作为一种通用的思路。

template<typename T>
void BinNode<T>::visitAlongLeftBranch(BinNodePosi(T) x, Stack<BinNodePosi(T)>& stack) {
    while (x) {
        cout << x->data << " ";
        if (x->rChild) stack.push(x->rChild);
        x = x->lChild;
    }

};

template<typename T>
void BinNode<T>::travPre2(BinNodePosi(T) x) {
    Stack<BinNodePosi(T)> stack;
    BinNodePosi(T) temp = x;
    while (true) {
        visitAlongLeftBranch(x, stack);
        if (stack.empty()) break;
        x = stack.pop();
    }
}

visitAlongLeftBranch,一边沿着左子树向下,一边遍历

中序遍历迭代实现

思路:

  1. 拿到一个节点,沿着他的左孩子一直向下,把根节点和他的左孩子依次压入stack中,直至nullptr

  1. 从stack拿出一个节点,访问(这个节点肯定是叶节点或者刚刚被遍历完左孩子的父节点)

  1. x=x->rChild,对他的右孩子进行goAlongLeftBranch

template<typename T>
void BinNode<T>::goAlongLeftBranch(BinNodePosi(T) x, Stack<BinNodePosi(T)>& stack) {
    while (x) {
        stack.push(x);
        x = x->lChild;
    }

};

template<typename T>
void BinNode<T>::travIn(BinNodePosi(T) x) {
    Stack<BinNodePosi(T)> stack;
    while (true) {
        goAlongLeftBranch(x, stack);
        if (stack.empty()) break;
        x = stack.pop();
        cout << x->data << " ";
        x = x->rChild;
    }
}

goAlongLeftBranch,沿着左子树向下,把父节点放入栈中,直到访问完左子树,再访问父节点。画个图就明白了。

后序遍历迭代实现

思路:

  1. stack顶部的一个节点,如果有右孩子,入栈,如果有左孩子,入栈

  1. 循环,直至来到左孩子的叶节点

  1. 输出,更新节点x

  1. stack顶部的一个节点,如果他不是x的父节点,就是x的右兄弟,这时候,进行步骤一;如果他是,直接输出即可,因为他的左孩子和右孩子都已经访问完了

template<typename T>
void BinNode<T>::goToLeftMostBranch( Stack<BinNodePosi(T)>& stack) {
    while (BinNodePosi(T) x=stack.top()) {
        if (x->lChild != nullptr) {
            if (x->rChild != nullptr)
                stack.push(x->rChild);
            stack.push(x->lChild);
        }
        else
            stack.push(x->lChild);
    }
    stack.pop();
};

template<typename T>
void BinNode<T>::travPost(BinNodePosi(T) x) {
    Stack<BinNodePosi(T)> stack;
    stack.push(x);
    BinNodePosi(T) tempNode = x;
    while (!stack.empty()) {
        if (stack.top() != x->parent) {//重点
            goToLeftMostBranch(stack);
        }
        x = stack.pop();
        cout << x->data << " ";
    }
};

重点在父节点判断的位置,这个地方有些糊涂。

重构(递归版本)

利用二叉树的中序遍历和其他的任意一个遍历,还原二叉树。当只有前序遍历和后序遍历的时候,没有办法区分哪个部分是左子树,哪个部分是右子树,就无法还原一个唯一的二叉树。

中序+前序,还原二叉树

思路:

  1. 前序遍历的第一个数字既为父节点

  1. 在中序遍历中找到父节点的位置,开头到父节点即为左子树,父节点到结尾即为右子树

  1. 递归

template<typename T>
BinNodePosi(T) BinNode<T>::getNodePostIn(Vector<T> inOrder, Vector<T> postOrder) {
    //后序、中序还原二叉树
    if (postOrder.size() == 0) return nullptr;
    T rootValue = postOrder[postOrder.size() - 1];
    BinNodePosi(T) root = new BinNode(rootValue);
    if (postOrder.size() == 1) return root;
    //找父节点的位置
    int index = 0;
    for (int i = 0; i < inOrder.size(); i++)
        if (rootValue == inOrder[i])
        {
            index = i;
            break;
        }

    root->lChild = getNodePostIn(Vector<T>(inOrder, 0, index), Vector<T>(postOrder, 0, index));
    root->rChild = getNodePostIn(Vector<T>(inOrder, index + 1, inOrder.size()), Vector<T>(postOrder, index, postOrder.size() - 1));
    return root;
};

中序+后序,还原二叉树

思路:

  1. 后序遍历的最后一个数字既为父节点

  1. 在中序遍历中找到父节点的位置,开头到父节点即为左子树,父节点到结尾即为右子树

  1. 递归

template<typename T>
BinNodePosi(T) BinNode<T>::getNodePreIn(Vector<T> inOrder, Vector<T> preOrder) {
    if (preOrder.size() == 0) return nullptr;
    T rootValue = preOrder[0];
    BinNodePosi(T) root = new BinNode(rootValue);
    if (preOrder.size() == 1) return root;
    //找父节点的位置
    int index = 0;
    for (int i = 0; i < inOrder.size(); i++)
        if (rootValue == inOrder[i])
        {
            index = i;
            break;
        }

    root->lChild = getNodePreIn(Vector<T>(inOrder, 0, index), Vector<T>(preOrder, 1, index + 1));
    root->rChild = getNodePreIn(Vector<T>(inOrder, index + 1, inOrder.size() + 1), Vector<T>(preOrder, index + 1, preOrder.size()));
    return root;
};

重点在找父节点,区分左右子树上。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值