树的定义
树是个节点的有限集。当n=0时,称为空树。在任意一棵非空树中应满足:
- 有且仅有一个特定的称为根的节点
- 当n>1时,其余节点可分为m(m>0)个互不相交的有限集。其中每个集合本身又是一棵树。
显然,树的定义是递归的,即在树的定义中又用到了其自身。其具有以下两个特点:
- 树的根节点没有前驱, 除根节点以外的所有节点都有且只有一个前驱。
- 树中的所有节点都可以有0个或者多个后缀。
二叉树的定义
二叉树是一种特殊的树形结构,其特点是每个节点至多只有两棵子树,并且二叉树的子树有左右子树之分,其次序不能任意颠倒。
二叉树与树类似,其定义也为递归的形式.二叉树是个节点的有限集
- 或者为空二叉树,即n=0。
- 或者由一个根节点和两个互不相交的被称为根的左子树和右子树组成。左子树和右子树又分别是一棵二叉树。
二叉树的性质
- 非空二叉树上的叶结点数等于度为2的结点数加1
- 非空二叉树的第k层上至多有个结点
- 高度为h的二叉树至多有个结点
- 对完全二叉树从上到下,从左到右的顺序依次编号,有已下关系
- 当i>1时,结点i的双亲的编号为
- 当时结点 i 的左孩子编号为2i,否则无左孩子。
- 当时结点 i 的右孩子编号为2i+1,否则无右孩子。
- 结点 i 所在层次为
- 具有n个结点的完全二叉树的高度为或
二叉树的存储结构
二叉树的存储结构也同样也可用顺序和链式两种结构存储。
顺序存储的思路是将完全二叉树上的结点自上而下,自左向右的进行编号后存储在数组中,一般的二叉树要添加空结点使其与完全二叉树对应后才能用顺序的方式存储,该方式在存储完全二叉树和满二叉树时有着较为高效的空间利用率。但是对于一般的二叉树需要添加空结点来反映二叉树结点中的逻辑关系,可能存在较大的空间浪费。
注:这种方式建议从下标为1的位置开始存储否则不满足性质4
链式存储的思路就是在结点中添加两条指针分别指向左孩子和右孩子,由于顺序存储的空间利用率较低,因此二叉树一般会选择使用链式存储结构进行存储。
类结构(链式)
//二叉树结点
template<typename T>
class BiTNode {
private:
T data;
BiTNode<T> *lchild, *rchild;
public:
BiTNode();
BiTNode(T data);
virtual ~BiTNode();
T getData() const;
void setData(T data);
BiTNode<T> *getLchild();
void setLchild(BiTNode<T> *lchild);
BiTNode<T> *getRchild();
void setRchild(BiTNode<T> *rchild);
void preOrder();
};
//二叉树
template<typename T>
class BiTree {
private:
BiTNode<T> *root;
public:
BiTree();
BiTNode<T> *getRoot();
void setRoot(BiTNode<T> *root);
//递归版本前序遍历
void PreOrder();
//迭代版本前序遍历
void PreOrderByIteration();
//层次遍历
void levelOrder();
};
支持的一些方法
//****************************************************************************************************** BiNode ************************************************************************************************
template<typename T>
BiTNode<T>::BiTNode() {
lchild = nullptr;
rchild = nullptr;
}
template<typename T>
BiTNode<T>::BiTNode(T data) {
this->data = data;
lchild = nullptr;
rchild = nullptr;
}
template<typename T>
T BiTNode<T>::getData() const {
return data;
}
template<typename T>
void BiTNode<T>::setData(T data) {
BiTNode::data = data;
}
template<typename T>
BiTNode<T> *BiTNode<T>::getLchild() {
return lchild;
}
template<typename T>
void BiTNode<T>::setLchild(BiTNode<T> *lchild) {
BiTNode::lchild = lchild;
}
template<typename T>
BiTNode<T> *BiTNode<T>::getRchild() {
return rchild;
}
template<typename T>
void BiTNode<T>::setRchild(BiTNode<T> *rchild) {
BiTNode::rchild = rchild;
}
/***
* 先序遍历,先打印根,再打印左子树,最终打印右子树
* 中序遍历,后序遍历的代码类似,仅需要改变if中的三条语句即可
* @tparam T
*/
template<typename T>
void BiTNode<T>::preOrder() {
if(this != nullptr){
cout<<this->data<<endl;
lchild->preOrder();
rchild->preOrder();
}
}
template<typename T>
BiTNode<T>::~BiTNode() {
if (lchild!= nullptr) delete lchild;
if (rchild!= nullptr) delete rchild;
}
//****************************************************************************************************** BiTree ************************************************************************************************
template<typename T>
BiTree<T>::BiTree() {
root = nullptr;
}
template<typename T>
BiTNode<T> *BiTree<T>::getRoot() {
return root;
}
template<typename T>
void BiTree<T>::setRoot(BiTNode<T> *root) {
BiTree::root = root;
}
template<typename T>
void BiTree<T>::PreOrder() {
BiTNode<T> *p = root;
root->preOrder();
}
template<typename T>
void BiTree<T>::PreOrderByIteration() {
stack<BiTNode<T> *> s;
auto p = getRoot();
while(p||!s.empty()){
if(p){
cout<<p->getData();
s.push(p);
p = p->getLchild();
}
else{
p = s.top()->getRchild();
s.pop();
}
}
}
template<typename T>
void BiTree<T>::levelOrder() {
CQueue<BiTNode<T> *> queue;
queue.EnQueue(root);
BiTNode<T> * p ;
while(!queue.isEmpty()){
queue.DeQueue(p);
if(p->getLchild()) queue.EnQueue(p->getLchild());
if(p->getRchild()) queue.EnQueue(p->getRchild());
cout<<p->getData()<<endl;
}
}
遍历是二叉树的重要方法,遍历可以分为四种分别是前序,中序,后序,层次。
- 前序遍历的顺序是根,左树,右树。
- 中序则是左树,根,右树。
- 后序是左树,右树,根。
- 层次遍历则是按层遍历(这本质上其实是图遍历中的广度优先算法)
总结
树形结构在应用中会时常碰到,例如国内的行政区划就是树形结构,一个省下会有多个市,一个市下又有多个区县。 在上述代码中只实现了递归的前序遍历和迭代的中序遍历以及层次遍历三种遍历方式。