二叉树
二叉树的定义与特性:
参考百度的二叉树定义:百度二叉树
二叉树类型:
(1)
完全二叉树——若设二叉树的高度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第h层有叶子结点,并且叶子结点都是从左到右依次排布,这就是完全二叉树。
(2)
满二叉树——除了叶结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉树。
(3)
平衡二叉树——平衡二叉树又被称为AVL树(区别于AVL算法),它是一棵二叉排序树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
二叉树的一些定理:
(1)在非空二叉树中,第i层的结点总数不超过2^(i-1)个, i>=1;
(2)深度为h的二叉树最多有2^h - 1个结点(h>=1),最少有h个结点;
(3)具有n个结点的完全二叉树的深度为log(n+1)
;
(4)
非空满二叉树
的叶结点数等于其分支结点数加1;
(5)一棵非空二叉树空子树的数目等于其结点数目加1。
二叉树的抽象数据类型
对于二叉树的操作,有些是关于结点的(如指向子结点或获取值),另一些则是关于整棵树的(如初始化)。这说明在c++中必须分别实现二叉树与结点的类。这里提供了一种二叉树结点类的定义。基本适合各种不同的二叉树类型。
下面是一棵二叉树结点的ADT,称为BinNode。
template <class Elem> class BinNode
{
public:
// 返回该结点的值
virtual Elem& val() = 0;
// 设置该结点的值
virtual void setVal(const Elem&) = 0;
// 返回左子节点
virtual BinNode* left() const = 0;
// 设置左子结点
virtual void setLeft(BinNode*) = 0;
// 返回右子节点
virtual BinNode* right() const = 0;
// 设置右子节点
virtual void setRight(BinNode*) = 0;
// 判断该结点是否是叶节点
virtual bool isLeaf() = 0;
};
使用指针实现二叉树
根据定义,每一个结点都有两个子结点,不论有一个为空还是二者都为空。通常,二叉树的结点都包含一个数据区,数据区所需空间大小根据需要而定。最常见的结点实现方法包含一个数据区和两个指向子结点的指针。下面给出了名为BinNodePtr的BinNod抽象类的简单实现。
template <class Elem>
class BinNodePtr : public BinNode<Elem>
{
private:
Elem element; // 二叉树指针结点存放的数据
BinNodePtr* leftChildNode;
BinNodePtr* rightChildNode;
public:
// 两个构造函数
BinNodePtr() { leftChildNode = rightChildNode = nullptr; }
BinNodePtr(Elem elemval, BinNodePtr* left = nullptr, BinNodePtr* right = nullptr)
{
element = elemval; leftChildNode = left; rightChildNode = right;
}
~BinNodePtr() {}
// 返回该结点的值
Elem& val() { return element; }
// 设置该结点的值
void setVal(const Elem& elem) { element = elem; }
// 返回左子节点
BinNode<Elem>* left() const { return leftChildNode; }
// 设置左子结点
void setLeft(BinNode<Elem>* node) { leftChildNode = (BinNodePtr*)node; }
// 返回右子节点
BinNode<Elem>* right() const { return rightChildNode; }
// 设置右子节点
void setRight(BinNode<Elem>* node) { rightChildNode = (BinNodePtr*)node; }
// 判断该结点是否是叶节点,通过判断左右子结点是否为空
bool isLeaf() { return (leftChildNode == nullptr) && (rightChildNode = nullptr); }
};
在利用指针实现的二叉树中,叶结点与分支节点是否使用相同的类定义十分重要。今后有一些应用只需要用叶结点存储数据,还有一些应用需求分支节点与叶指点存储不同类型的数据。根据定义,只有分支节点有非空子结点。因此,分别定义分支结点与叶节点将节省存储空间。