此文章为从二叉树到红黑树系列文章的第一节,主要介绍写这系列文章的起因,致谢邓老师,解释二叉树节点类和二叉树的四种遍历写法(包括递归和迭代写法)
文章目录
一、前言与致谢~(点击右边波浪线可以返回目录)
c++是一门与时俱进的语言,纵观网上绝大多数c++的数据结构实现,都是c with class,不得不说,这样确实有利于理解和学习,但对于那些想借助数据结构来巩固c++的人来说,不由得有点遗憾(初学直接啃stl源码会直接自闭的)。
在此,感谢清华的邓俊辉老师的c++数据结构教材和视频,多亏了邓老师的辛勤耕耘,我才有幸见到现代化的适用于初学者的c++数据结构代码。在邓老师教材的第五章,第七章和第八章,全部是介绍树的知识,看完和仿照敲完代码之后,获益匪浅,不止是对数据结构和算法的理解更深了一步,也对c++的特性掌握的更加牢固了一步。
笔者将其代码中的一些特性进行了一些更新和修改,让其更符合现代风格。由于老师对堆区数据的回收都处理的很好,有的地方用智能指针的话,需要改比较多的东西,所以还是保持原来的new和delete。
本系列文章为了方便,会引用教材和视频中的一些插图(侵删),虽然我对原代码进行了些许修改,但原代码所有权依然归邓老师所有。
在写此系列文章的时候,笔者也对文章进行了排版,让其更容易进行阅读,大家放心阅读,笔者在适当的地方都留了空行和分段。(md的排版真心没word好= =,排个版都要敲代码!)
本系列文章的所有代码,均在vs2019版本下编译通过,如果你无法编译过,请把编译器的c++版本开启c++17以上。
二、本系列文章内容基本介绍~
1.与邓老师原有代码对比~
我主要对以下几点进行了更新和修改
- 将所有的宏定义换成了c++11的constexpr和inline。有的换成了全局静态函数,有的换成了类中使用的函数。
- 大概是处于教学需要,邓公用的都是自定义的stack和queue,笔者就偷懒,直接用stl现成的stack和queue了,实现上大致相同。
- 将原来的某些代码进行了增减,这样更便于呈现主要内容。
- 所有主要代码都有迭代形式,主要代码流程中没有递归,有的代码会分别给出递归和迭代的形式,以便于理解。
2.主要流程(重点)~
笔者根据代码的内容不同,将分六部分来展现
- 基本二叉树节点,通用函数
- 基本二叉树类的定义和实现
- BST(二叉搜索树的实现)
- AVL(二叉平衡搜索树的实现)
- B树的实现(如果你只想了解B树,可以跳过所有章节,直接看B树)
- 红黑树的实现
强烈建议按照流程进行浏览,只有这样,你才能真正了解红黑树!
但若你没有时间,也没关系,我会在一些重点部分,涉及到之前知识点的地方弄一个提示,去指明你去哪才能看到相关的内容。
3.要看懂文章内的代码你需要了解的内容~
- 掌握c++的引用,指针,类的基本使用
- 知道什么是指针引用
- 了解命名空间,using声明,using别名。
- 了解c++的模板,仿函数的使用
- 熟悉stl中stack和queue容器的用法
- 对constexpr和inline不陌生
- 对树有一定的了解
要完全看懂本系列文章,如果你是初次接触,不要妄想能在两三个小时内弄懂此系列文章所有内容,没人可以做的到,正确的方法是一天抽一两个小时研究其中一篇文章,对于红黑树可能需要更长的时间,你才能真正的看懂代码,如果你看过邓老师的教程的话,会更能体会到我这段话的意思。
如果你嫌长,大可以看其他的文章,但我相信,弄懂本系列文章之后,不仅你的数据结构的功底,还是c++的功底,都可以更上一层楼。(针对c++初学者)。
三、树的语义规定~
1.树的高度与深度~
在此系列文章中,会大量用到树的高度和深度的概念,在此做一个强调
深度: 从根到该节点的唯一路径长,根的深度为0;从上到下数
高度: 从该节点到叶节点的最长路径长,所有树叶的高度为0;从下往上数
另外,规定空树的高度为-1。
所以,若只有根节点存在,根节点的高度为0。
2.树的遍历~
(1)树的先序遍历~
(2)树的中序遍历~
(3)树的后序遍历~
(4)树的层次遍历~
四、基本二叉树节点类~
(一)定义变量和接口~
1.需要的变量~
T _data;//存放数据
BinNodePtr _parent;
BinNodePtr _lchild;
BinNodePtr _rchild;
int _height;//高度(通用)
RBColor _color;//红黑树专用
2.需要的接口~
四种遍历
取得当前节点的直接后继(BST,AVL,RedBlack用)
3.其他辅助函数~
判等器等操作符重载
4.BinNode.h~
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函数,将用的少的放入对应的树的类中,这样更有利于管理和应用。
BInNode_Macro.h~
#pragma once
/******************************************************************************************
* BinNode状态与性质的判断
******************************************************************************************/
#include<algorithm>
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中,我都会提供一些测试用的仿函数,读者可根据自己的需要进行改进。
1.层次遍历的队列写法~
根节点入队,然后弹出根节点,并将其左右节点入队。直至队列为空。
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(