此文章为从二叉树到红黑树系列文章的第一节,主要介绍写这系列文章的起因,致谢邓老师,解释二叉树节点类和二叉树的四种遍历写法(包括递归和迭代写法)
c++是一门与时俱进的语言,纵观网上绝大多数c++的数据结构实现,都是c with class,不得不说,这样确实有利于理解和学习,但对于那些想借助数据结构来巩固c++的人来说,不由得有点遗憾(初学直接啃stl源码会直接自闭的)。
在此,感谢清华的邓俊辉老师的c++数据结构教材和视频,多亏了邓老师的辛勤耕耘,我才有幸见到现代化的适用于初学者的c++数据结构代码。在邓老师教材的第五章,第七章和第八章,全部是介绍树的知识,看完和仿照敲完代码之后,获益匪浅,不止是对数据结构和算法的理解更深了一步,也对c++的特性掌握的更加牢固了一步。
笔者将其代码中的一些特性进行了一些更新和修改,让其更符合现代风格。由于老师对堆区数据的回收都处理的很好,有的地方用智能指针的话,需要改比较多的东西,所以还是保持原来的new和delete。
本系列文章为了方便,会引用教材和视频中的一些插图(侵删),虽然我对原代码进行了些许修改,但原代码所有权依然归邓老师所有。
在写此系列文章的时候,笔者也对文章进行了排版,让其更容易进行阅读,大家放心阅读,笔者在适当的地方都留了空行和分段。(md的排版真心没word好= =,排个版都要敲代码!)
本系列文章的所有代码,均在vs2019版本下编译通过,如果你无法编译过,请把编译器的c++版本开启c++17以上。
我主要对以下几点进行了更新和修改
笔者根据代码的内容不同,将分六部分来展现
强烈建议按照流程进行浏览,只有这样,你才能真正了解红黑树!
但若你没有时间,也没关系,我会在一些重点部分,涉及到之前知识点的地方弄一个提示,去指明你去哪才能看到相关的内容。
要完全看懂本系列文章,如果你是初次接触,不要妄想能在两三个小时内弄懂此系列文章所有内容,没人可以做的到,正确的方法是一天抽一两个小时研究其中一篇文章,对于红黑树可能需要更长的时间,你才能真正的看懂代码,如果你看过邓老师的教程的话,会更能体会到我这段话的意思。
如果你嫌长,大可以看其他的文章,但我相信,弄懂本系列文章之后,不仅你的数据结构的功底,还是c++的功底,都可以更上一层楼。(针对c++初学者)。
在此系列文章中,会大量用到树的高度和深度的概念,在此做一个强调
深度: 从根到该节点的唯一路径长,根的深度为0;从上到下数
高度: 从该节点到叶节点的最长路径长,所有树叶的高度为0;从下往上数
另外,规定空树的高度为-1。
所以,若只有根节点存在,根节点的高度为0。
T _data;//存放数据
BinNodePtr _parent;
BinNodePtr _lchild;
BinNodePtr _rchild;
int _height;//高度(通用)
RBColor _color;//红黑树专用
四种遍历
取得当前节点的直接后继(BST,AVL,RedBlack用)
判等器等操作符重载
namespace {
enum class RBColor {
RED, BLACK };
}
//===二叉树节点类===//
template<typename T = int>
class BinNode {
public:
using BinNodePtr = BinNode<T>*;
public:
T _data;//存放数据
BinNodePtr _parent;
BinNodePtr _lchild;
BinNodePtr _rchild;
int _height;//高度(通用)
RBColor _color;//红黑树专用
public:
BinNode() :_data(0), _parent(nullptr),_lchild(nullptr), _rchild(nullptr), _height(0), _color(RBColor::RED) {
}
BinNode(
const T& data,
const BinNodePtr parent = nullptr,
const BinNodePtr lchild=nullptr,
const BinNodePtr rchild=nullptr,
const int &height=0,
const RBColor& color = RBColor::RED)
:_data(data), _parent(parent), _lchild(lchild), _rchild(rchild),_height(height),_color(color) {
}
public:
constexpr bool operator==(const BinNode& bN)const {
return this->_data == bN._data;
}
constexpr bool operator!=(const BinNode& bN)const {
return !(this->_data == bN._data);
}
constexpr bool operator<(const BinNode& bN)const {
return this->_data < bN._data;
}
constexpr bool operator>(const BinNode& bN)const {
return this->_data > bN._data;
}
constexpr bool operator<=(const BinNode& bN)const {
return !(this->_data > bN._data);
}
constexpr bool operator>=(const BinNode& bN)const {
return !(this->_data < bN._data);
}
public:
BinNodePtr succ();//取得中序遍历当前节点的直接后继,必然无左孩子
public:
template <typename VST> void travLevel(VST); //子树层次遍历
template <typename VST> void travPre(VST,const int &method=1); //子树先序遍历
template <typename VST> void travPost(VST,const int&method=1); //子树后序遍历
template <typename VST> void travIn(VST,const int&method=1); //子树中序遍历
};//class BinNode
邓老师书上的宏定义有很多个,我将用的多的封装成相应的constexpr和inline函数,将用的少的放入对应的树的类中,这样更有利于管理和应用。
#pragma once
/******************************************************************************************
* BinNode状态与性质的判断
******************************************************************************************/
#include
using std::max;
namespace mytree_marcro {
template<typename BinNodePtr>
static constexpr bool IsRoot(const BinNodePtr& x) {
return !(x->_parent);
}
template<typename BinNodePtr>
static constexpr bool IsLChild(const BinNodePtr& x) {
return (!IsRoot(x) && (x == x->_parent->_lchild));
}
template<typename BinNodePtr>
static constexpr bool IsRChild(const BinNodePtr& x) {
return (!IsRoot(x) && (x == x->_parent->_rchild));//不是根节点,并且其地址跟其父亲的右孩子地址相同
}
template<typename BinNodePtr>
static constexpr bool HasLChild(const BinNodePtr& x) {
return (x == nullptr) ? false : x->_lchild;
}
template<typename BinNodePtr>
static constexpr bool HasRChild(const BinNodePtr& x) {
return (x == nullptr) ? false : x->_rchild;
}
template<typename BinNodePtr>
static constexpr int stature(const BinNodePtr& x) {
//获取高度
return x ? x->_height : -1;//空指针高度为-1
}
}// namespace mytree_marcro
对于老手和善用stl的人而言, 这种利用回调函数来进行访问对应节点的方式,再正常不过。在邓老师的遍历代码中,就高明的用了这种方式,通过这种高内聚,低耦合的方式,从而能对节点进行更多的操作,而并非仅仅是cout就完事。在之后的BST,AVL和RedBlack中,我都会提供一些测试用的仿函数,读者可根据自己的需要进行改进。
根节点入队,然后弹出根节点,并将其左右节点入队。直至队列为空。
template<typename T>template<typename VST>
void BinNode<T>::travLevel(VST visit) {
if (this == nullptr)//提前判断是否为空树
return;
queue<BinNodePtr> Q;
Q.push(this);//根节点入队
BinNodePtr current = nullptr;//防止多次调用构造析构,故在循环为创建临时变量
while (!Q.empty()) {
//如果队列不为空
current = Q.front();
visit(current);
Q.pop();
if (HasLChild(current))
Q.push(current->_lchild);
if (HasRChild(current))
Q.push(current->_rchild);
}
}
由于有三种先序遍历的写法,因此创建一个接口函数,并且默认调用迭代版的先序遍历
template<typename T>template<typename VST>
void BinNode<T>::travPre(VST visit,const int& method) {
using mytree_trav::travPre_1;
using mytree_trav::travPre_2;
using mytree_trav::travPre_R;
if (this == nullptr)//提前判断是否为空树
return;
switch (method)
{
case 0:travPre_1(this, visit); break;
case 1:travPre_2(this, visit); break;
default:travPre_R(this, visit); break;
}
}
以根左右的顺序,进行递归先序遍历
/*先序遍历递归版*/
template<typename BinNodePtr, typename VST>
static void travPre_R(BinNodePtr x, VST visit) {
if (x == nullptr)
return;
visit(x);
travPre_R(x->_lchild, visit);
travPre_R(x->_rchild, visit);
}
尾递归转化为循环。如果只有一个递归体,那么直接转循环就可以。因为此处有两个递归体,因此,还需要借助一个辅助栈来实现。
递归时,分成3部分来看,第一部分基本语句,第二部分递归调用左孩子,第三部分递归调用右孩子。
从递归时函数调用的堆栈分析来看,相当于先把最后一个语句,也就是遍历右孩子入栈,再把倒数第二个语句,也就是遍历左孩子入栈,下一步就是取出栈顶元素进行遍历。重复循环。(理解了此点,你就能很快的理解为什么需要一个辅助栈!)
//-----------先序遍历--------------//
/*迭代版1*//*递归转迭代*//*此法与迭代2在效率上,没什么区别,但是此法只适用于先序
*而迭代2的方式,可以适用于中序和后序遍历*/
template<typename BinNodePtr, typename VST>
static void travPre_1(BinNodePtr x, VST visit) {
stack<BinNodePtr> S;
S.push(x);
while (!S.empty()) {
x = S.top();
S.pop();
visit(x);
if (HasRChild(x))//入栈次序为先右后左
S.push(x->_rchild);
if (HasLChild(x))
S.push(x->_lchild);
}
}
从书上虚线的的路径,不难发现遍历过程中的一个套路,即遍历可以等价的理解成,从当前节点从上到下访问左子树,并visit对应的节点,直到访问到左子树为空为止,同时在途中将右孩子均存入栈中。当左子树访问到根节点时,就取出栈顶元素,继续做一次从上到下的向左遍历。
/*迭代版2*/
template<typename BinNodePtr, typename VST>
static void visitLeft_Pre(BinNodePtr x, VST visit, stack<BinNodePtr>& S) {
while (x) {
visit(x);//访问当前节点
if (HasRChild(x))//右孩子入栈暂存
S.push(x->_rchild);
x = x->_lchild;
}
}
template<typename BinNodePtr, typename VST>
static void travPre_2(BinNodePtr x, VST visit) {
stack<BinNodePtr> S;
while (true) {
visitLeft_Pre(x, visit, S);//从当前节点出发,从上到下,向左访问。
if (S.empty())//直到栈空
break;
x = S.top();//若栈不空,则取出栈顶节点继续循环
S.pop();
}
}
由于有三种中序遍历的写法,因此创建一个接口函数,并且默认调用迭代版的中序遍历
template<typename T>template<typename VST>
void BinNode<T>::travIn(VST visit, const int& method) {
using mytree_trav::travIn_1;
using mytree_trav::travIn_2;
using mytree_trav::travIn_R;
if (this == nullptr)//提前判断是否为空树
return;
switch (method)
{
case 1:travIn_1(this, visit); break;//1相对普通树最快
case 2:travIn_2(this, visit); break;
default:travIn_R(this, visit); break;
}
}
以左根右的顺序,进行递归中序遍历
/*递归版*/
template<typename BinNodePtr,typename VST>
static void travIn_R(BinNodePtr x, VST visit) {
if (x == nullptr)
return;
travIn_R(x->_lchild,visit);
visit(x);
travIn_R(x->_rchild,visit);
}
从上图可以看出,我们遍历时第一个访问的必然是最左边的节点a,但我们的传入的节点一般为根节点,即都是从i开始进行中序遍历的。因此,首先也必须要到最左边的节点a,即要做一次从上到下的向左访问左子树,而沿途的节点都不妨存入栈中,方便之后使用。
每从栈中弹出一个节点,就对其进行visit,都需要对其有没有右孩子进行检查,如果有右孩子,则右孩子入栈,并且对右孩子也做一次从上到下的向左遍历,将沿途的节点全部存入栈中。
//---------中序遍历---------//
template<typename BinNodePtr>
static void visitLeft_In(BinNodePtr x, stack<BinNodePtr>& S) {
//将所有左孩子插入栈中
while (x) {
S.push(x);
x = x->_lchild;
}
}
/*迭代版1*/
template<typename BinNodePtr, typename VST>
static void travIn_1(BinNodePtr x, VST visit) {
stack<BinNodePtr> S;
while (true) {
visitLeft_In(x, S);//将所有左孩子插入栈中
if (S.empty())
break;
x = S.top();
visit(x);
S.pop();
x = x->_rchild;//如果栈顶元素有右孩子,则对右孩子的左子树进行visitLeft_In,若右孩子为空,则直接结束visitLeft_In
}
}
本方法实际上本人不推荐使用,因为其每一次遍历都需要去求后继,无疑相对前面一种迭代方法而言,增加了不必要的代价。
因此,此法有兴趣者可以看看。此法最重要的是一种利用后继来遍历的思想,这点对于线索二叉树而言非常有效。有兴趣的读者可以看看本人按照大话数据结构改编的线索二叉树的c++代码https://blog.csdn.net/bioinformatique/article/details/106082423
/*迭代版2*/
template<typename BinNodePtr, typename VST>
static void travIn_2(BinNodePtr x, VST visit) {
while (true) {
if (HasLChild(x)) {
//若有左子树,则
x = x->_lchild;//深入左子树
}
else {
visit(x);//访问当前节点,并
while (!HasRChild(x)) {
//不断地在无右分支处
if (x = x->succ())//回溯至直接后继,注意此处为赋值
visit(x);//如果有后继,则访问
else
return; //(在没有后继的末节点处,直接退出)
}
x = x->_rchild;//(直至有右分支处)转向非空的右子树
}
}
}
由于有两种后序遍历的写法,因此创建一个接口函数,并且默认调用迭代版的后序遍历
template<typename T>template<typename VST>
void BinNode<T>::travPost(VST visit, const int& method) {
using mytree_trav::travPost_1;
using mytree_trav::travPost_R;
if (this == nullptr)//提前判断是否为空树
return;
switch (method)
{
case 1:travPost_1(this, visit); break;
default:travPost_R(this, visit); break;
}
}
以左右根的顺序,进行递归后序遍历
/*递归版*/
template<typename BinNodePtr, typename VST>
static void travPost_R(BinNodePtr x, VST visit) {
if (x == nullptr)
return;
travPost_R(x->_lchild, visit);
travPost_R(x->_rchild, visit);
visit(x);
}
后序遍历与中序和先序遍历不同的是,后序遍历的第一个被访问的节点稍有不同,其没有先序(必然为根节点)和中序(必然为最左孩子)这样的规律性。
1. 若最左边孩子没有右子树(最左边孩子显然没有左子树),则最左边节点是第一个被访问的节点。
2. 若最左边孩子有右子树,则是最左边的节点的右子树的最左边的节点。
首先,无论如何,都需要找到第一个节点。并且沿途的节点也要像中序和前序遍历一样很好地利用起来。幸运的是,邓老师已经帮我们解决了这个难题。
方法就是从当前节点开始,先看其有没有右孩子,如果有右孩子,则将右孩子入栈,然后再看有没有左孩子,如果有左孩子,则将左孩子再入栈,并且将左孩子当做“当前节点”继续循环。
如果当前节点没有左孩子,则看这个节点的右孩子有没有孩子,如果这个节点的右孩子有孩子,则把这个节点作为“当前节点”,继续进行循环。
直到“当前节点”没有孩子为止。那么,此时的栈顶元素,必将为当前树(子树)的后序遍历应该访问的第一个节点。
template<typename BinNodePtr>
static void visitLeft_Post(stack<BinNodePtr>& S) {
BinNodePtr x = S.top();//将栈顶节点作为当前节点
while (true) {
//判断是否有左右孩子,并且先插入右孩子
if (HasRChild(x)) {
S.push(x->_rchild);
}
if (HasLChild(x)) {
S.push(x->_lchild);
}
if (x == S.top())//说明栈没有插入任何元素,即抵达此子树应该被访问的第一个节点
break;
x = S.top();//否则就将栈顶元素给当前的x
}
}
在解决了找到当前子树的相对后继遍历的第一个节点后,就解决了后继遍历的绝大多数问题。并且,并非在遍历过程中,所有的节点都需要去执行visitLeft_Post函数。
我们需要对b进行一次visitLeft_Post函数么,显然不需要,因为其没有左子树。
因此直接访问b。
下一个被访问的元素为G么?,显然不是G,其左右子树也都没有在栈中,因此,我们需要以G为子树进行一次visitLeft_Post函数,从而找到c。
从上面3个图可以看出,访问b前不需要进行visitLeft_Post函数,访问c时需要进行visitLeft_Post函数。那么,到底什么时候需要进行visitLeft_Post函数?
不妨看一下父子关系。b是a的父亲,而G不是b的父亲,反而,G是b的兄弟节点!
不妨可以得出结论,只有当此时栈顶元素不是刚刚弹出的栈顶元素的父亲时(必然为刚弹出元素的兄弟),才会进行visitLeft_Post函数,将新的节点插入栈中。
下面给出后序遍历迭代版的算法
/*迭代版*/
template<typename BinNodePtr, typename VST>
static void travPost_1(BinNodePtr x, VST visit) {
stack<BinNodePtr> S;
S.push(x);
while (!S.empty()) {
//若栈顶节点不是父亲节点,即必为其兄弟节点(除了根节点以外),
//若为其兄弟节点,则对兄弟节点进行visitLeft_Post的操作。
if (S.top() != x->_parent)
visitLeft_Post(S);
visit(S.top());
x = S.top();//将x设为栈顶元素
S.pop();
}
}
//--------------后序遍历--------------//
template<typename BinNodePtr>
static void visitLeft_Post(stack<BinNodePtr>& S) {
BinNodePtr x = S.top();//将栈顶节点作为当前节点
while (true) {
//判断是否有左右孩子,并且先插入右孩子
if (HasRChild(x)) {
S.push(x->_rchild);
}
if (HasLChild(x)) {
S.push(x->_lchild);
}
if (x == S.top())//说明栈没有插入任何元素,即抵达此子树应该被访问的第一个节点
break;
x = S.top();//否则就将栈顶元素给当前的x
}
}
/*迭代版*/
template<typename BinNodePtr, typename VST>
static void travPost_1(BinNodePtr x, VST visit) {
stack<BinNodePtr> S;
S.push(x);
while (!S.empty()) {
//若栈顶节点不是父亲节点,即必为其兄弟节点(除了根节点以外),
//若为其兄弟节点,则对兄弟节点进行visitLeft_Post的操作。
if (S.top() != x->_parent)
visitLeft_Post(S);
visit(S.top());
x = S.top();//将x设为栈顶元素
S.pop();
}
}
succ()取得当前节点的后继节点
与所有遍历一样,中序遍历的实质功能也可理解为,为所有节点赋予一个次序,从而将半线性的二叉树转化为线性结构。于是一旦指定了遍历策略,即可与向量和列表一样,在二叉树的节点之间定义后继关系。其中没有后继)的节点称作末节点。
对于后面将要介绍的BST,AVL,RedBlack,中序遍历的作用至关重要。相关算法必需的一项基本操作,就是定位任一节点在中序遍历序列中的直接后继。
分析一个节点的后继,不妨分做两种情况分别进行考虑。
在这种情况下,当前节点的直接后继,必然没有左孩子。
//-------取得当前节点的直接后继----------//
template<typename T>
BinNode<T>* BinNode<T>::succ() {
using BinNodePtr = BinNode<T>*;
BinNodePtr s = this;
if (HasRChild(s)) {
//当前节点有右孩子
s = s->_rchild;
while (HasLChild(s))//这个右孩子有没有左孩子,直到没有左孩子为止
s = s->_lchild;
}
else {
//必须分是否是右孩子来判断,因为这对应不同的情况,如果这个节点不为右孩子,则返回其父亲
//如果这个节点是右孩子,则需要不断向上找其父亲,直到不为右孩子为止(即为左孩子或根节点)
while (IsRChild(s))
s = s->_parent;
s = s->_parent;//再往上找一格,就能找到后继。//当然,如果已经到了根节点,根节点的父亲当然为空。所以返回空。同时也说明这个节点是最后一个节点。
}
return s;
}
#pragma once
#include
#include
#include "BinNode_Macro.h"
using std::queue;
using std::stack;
namespace mytree {
using namespace mytree_marcro;
namespace {
enum class RBColor {
RED, BLACK };
}
//===二叉树节点类===//
template<typename T = int>
class BinNode {
public:
using BinNodePtr = BinNode<T>*;
public:
T _data;//存放数据
BinNodePtr _parent;
BinNodePtr _lchild;
BinNodePtr _rchild;
int _height;//高度(通用)
RBColor _color;//红黑树专用
public:
BinNode() :_data(0), _parent(nullptr),_lchild(nullptr), _rchild(nullptr), _height(0), _color(RBColor::RED) {
}
BinNode(
const T& data,
const BinNodePtr parent = nullptr,
const BinNodePtr lchild=nullptr,
const BinNodePtr rchild=nullptr,
const int &height=0,
const RBColor& color = RBColor::RED)
:_data(data), _parent(parent), _lchild(lchild), _rchild(rchild),_height(height),_color(color) {
}
public:
constexpr bool operator==(const BinNode& bN)const {
return this->_data == bN._data;
}
constexpr bool operator!=(const BinNode& bN)const {
return !(this->_data == bN._data);
}
constexpr bool operator<(const BinNode& bN)const {
return this->_data < bN._data;
}
constexpr bool operator>(const BinNode& bN)const {
return this->_data > bN._data;
}
constexpr bool operator<=(const BinNode& bN)const {
return !(this->_data > bN._data);
}
constexpr bool operator>=(const BinNode& bN)const {
return !(this->_data < bN._data);
}
public:
BinNodePtr succ();//取得中序遍历当前节点的直接后继,必然无左孩子
public:
template <typename VST> void travLevel(VST); //子树层次遍历
template <typename VST> void travPre(VST,const int &method=1); //子树先序遍历
template <typename VST> void travPost(VST,const int&method=1); //子树后序遍历
template <typename VST> void travIn(VST,const int&method=1); //子树中序遍历
};//class BinNode
//--------------遍历------------------//
namespace mytree_trav {
//-----------先序遍历--------------//
/*迭代版1*//*递归转迭代*//*此法与迭代2在效率上,没什么区别,但是此法只适用于先序
*而迭代2的方式,可以适用于中序和后序遍历*/
template<typename BinNodePtr, typename VST>
static void travPre_1(BinNodePtr x, VST visit) {
stack<BinNodePtr> S;
S.push(x);
while (!S.empty()) {
x = S.top();
S.pop();
visit(x);
if (HasRChild(x))//入栈次序为先右后左
S.push(x->_rchild);
if (HasLChild(x))
S.push(x->_lchild);
}
}
/*迭代版2*/
template<typename BinNodePtr, typename VST>
static void visitLeft_Pre(BinNodePtr x, VST visit, stack<BinNodePtr>& S) {
while (x) {
visit(x);//访问当前节点
if (HasRChild(x))//右孩子入栈暂存
S.push(x->_rchild);
x = x->_lchild;
}
}
template<typename BinNodePtr, typename VST>
static void travPre_2(BinNodePtr x, VST visit) {
stack<BinNodePtr> S;
while (true) {
visitLeft_Pre(x, visit, S);//从当前节点出发,从上到下,向左访问。
if (S.empty())//直到栈空
break;
x = S.top();//若栈不空,则取出栈顶节点继续循环
S.pop();
}
}
/*先序遍历递归版*/
template<typename BinNodePtr, typename VST>
static void travPre_R(BinNodePtr x, VST visit) {
if (x == nullptr)
return;
visit(x);
travPre_R(x->_lchild, visit);
travPre_R(x->_rchild, visit);
}
//--------------后序遍历--------------//
template<typename BinNodePtr>
static void visitLeft_Post(stack<BinNodePtr>& S) {
BinNodePtr x = S.top();//将栈顶节点作为当前节点
while (true) {
//判断是否有左右孩子,并且先插入右孩子
if (HasRChild(x)) {
S.push(x->_rchild);
}
if (HasLChild(x)) {
S.push(x->_lchild);
}
if (x == S.top())//说明栈没有插入任何元素,即抵达此子树应该被访问的第一个节点
break;
x = S.top();//否则就将栈顶元素给当前的x
}
}
/*迭代版*/
template<typename BinNodePtr, typename VST>
static void travPost_1(BinNodePtr x, VST visit) {
stack<BinNodePtr> S;
S.push(x);
while (!S.empty()) {
//若栈顶节点不是父亲节点,即必为其兄弟节点(除了根节点以外),若为其兄弟节点,则对兄弟节点进行visitLeft_Post的操作。
if (S.top() != x->_parent)
visitLeft_Post(S);
visit(S.top());
x = S.top();//将x设为栈顶元素
S.pop();
}
}
/*递归版*/
template<typename BinNodePtr, typename VST>
static void travPost_R(BinNodePtr x, VST visit) {
if (x == nullptr)
return;
travPost_R(x->_lchild, visit);
travPost_R(x->_rchild, visit);
visit(x);
}
//---------中序遍历---------//
template<typename BinNodePtr>
static void visitLeft_In(BinNodePtr x, stack<BinNodePtr>& S) {
//将所有左孩子插入栈中
while (x) {
S.push(x);
x = x->_lchild;
}
}
/*迭代版1*/
template<typename BinNodePtr, typename VST>
static void travIn_1(BinNodePtr x, VST visit) {
stack<BinNodePtr> S;
while (true) {
visitLeft_In(x, S);//将所有左孩子插入栈中
if (S.empty())
break;
x = S.top();
visit(x);
S.pop();
x = x->_rchild;//如果栈顶元素有右孩子,则对右孩子的左子树进行visitLeft_In,若右孩子为空,则直接结束visitLeft_In
}
}
/*迭代版2*/
template<typename BinNodePtr, typename VST>
static void travIn_2(BinNodePtr x, VST visit) {
while (true) {
if (HasLChild(x)) {
//若有左子树,则
x = x->_lchild;//深入左子树
}
else {
visit(x);//访问当前节点,并
while (!HasRChild(x)) {
//不断地在无右分支处
if (x = x->succ())//回溯至直接后继,注意此处为赋值
visit(x);//如果有后继,则访问
else
return; //(在没有后继的末节点处,直接退出)
}
x = x->_rchild;//(直至有右分支处)转向非空的右子树
}
}
}
/*递归版*/
template<typename BinNodePtr,typename VST>
static void travIn_R(BinNodePtr x, VST visit) {
if (x == nullptr)
return;
travIn_R(x->_lchild,visit);
visit(x);
travIn_R(x->_rchild,visit);
}
}//namespace mytree_trav
template<typename T>template<typename VST>
void BinNode<T>::travLevel(VST visit) {
if (this == nullptr)//提前判断是否为空树
return;
queue<BinNodePtr> Q;
Q.push(this);//根节点入队
BinNodePtr current = nullptr;//防止多次调用构造析构,故在循环为创建临时变量
while (!Q.empty()) {
//如果队列不为空
current = Q.front();
visit(current);
Q.pop();
if (HasLChild(current))
Q.push(current->_lchild);
if (HasRChild(current))
Q.push(current->_rchild);
}
}
template<typename T>template<typename VST>
void BinNode<T>::travPre(VST visit,const int& method) {
using mytree_trav::travPre_1;
using mytree_trav::travPre_2;
using mytree_trav::travPre_R;
if (this == nullptr)//提前判断是否为空树
return;
switch (method)
{
case 0:travPre_1(this, visit); break;
case 1:travPre_2(this, visit); break;
default:travPre_R(this, visit); break;
}
}
template<typename T>template<typename VST>
void BinNode<T>::travPost(VST visit, const int& method) {
using mytree_trav::travPost_1;
using mytree_trav::travPost_R;
if (this == nullptr)//提前判断是否为空树
return;
switch (method)
{
case 1:travPost_1(this, visit); break;
default:travPost_R(this, visit); break;
}
}
template<typename T>template<typename VST>
void BinNode<T>::travIn(VST visit, const int& method) {
using mytree_trav::travIn_1;
using mytree_trav::travIn_2;
using mytree_trav::travIn_R;
if (this == nullptr)//提前判断是否为空树
return;
switch (method)
{
case 1:travIn_1(this, visit); break;//1相对普通树最快
case 2:travIn_2(this, visit); break;
default:travIn_R(this, visit); break;
}
}
//-------取得当前节点的直接后继----------//
template<typename T>
BinNode<T>* BinNode<T>::succ() {
using BinNodePtr = BinNode<T>*;
BinNodePtr s = this;
if (HasRChild(s)) {
//当前节点有右孩子
s = s->_rchild;
while (HasLChild(s))//这个右孩子有没有左孩子,直到没有左孩子为止
s = s->_lchild;
}
else {
//必须分是否是右孩子来判断,因为这对应不同的情况,如果这个节点不为右孩子,则返回其父亲
//如果这个节点是右孩子,则需要不断向上找其父亲,直到不为右孩子为止(即为左孩子或根节点)
while (IsRChild(s))
s = s->_parent;
s = s->_parent;//再往上找一格,就能找到后继。//当然,如果已经到了根节点,根节点的父亲当然为空。所以返回空。同时也说明这个节点是最后一个节点。
}
return s;
}
}//namespace mytree
学一个东西,不知道其道理,不高明!