目录
前言
本篇可以助你开始了解二叉树。
一、二叉树的逻辑结构
1.二叉树的定义
二叉树是n(n>=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵五互不相交的、分别称为根节点的左子树和右子树的二叉树组成。
二叉树的特点:
- 每个结点最多有两棵子树,所以二叉树中不存在度大于2的结点
- 二叉树是有序的,其次序不能任意颠倒,即使树中某个结点只有一棵树,也要区分它是左子树还是右子树。所以,二叉树和树是两种树结构。
二叉树的五种基本形态:
- 空二叉树
- 只有一个根结点
- 根结点只有左子树
- 根结点只有右子树
- 根结点既有左子树又有右子树
几种特殊的二叉树:
- 斜树
所有结点都只有左子树或都只有右子树
- 满二叉树:
所有分支节点都存在左子树和右子树,并且所有叶子都在同一层上
特点:
- 叶子只能出现在最下一层
- 只有度为0和度为2的结点
- 完全二叉树
对一棵具有n个结点的二叉树按层序编号,编号为i(1<=i<=n)的结点与同深度的满二叉树中编号为i的结点在二叉树中的位置相同
特点:
- 叶子结点只能出现在最下两层,且最下层的叶子结点都集中在二叉树左侧连续的位置
- 如果有度为1的结点,只可能有一个,且该结点只有左孩子
2.二叉树的基本性质
- 二叉树的第i层上最多有个结点(i>=1)
- 一棵深度为k的二叉树中,最多有个结点(满二叉树),最少有k个结点(可以是斜树)
- 在一棵二叉树中,如果叶子结点的个数为,度为2的结点个数为,则=+1
- 具有n个结点的完全二叉树的深度为
- 对一棵具有n个结点的完全二叉树中的结点从1开始按层序编号,则对于任意的编号为i(i<=i<=n)的结点(简称为结点i),有:
(1)如果i>1,则结点i的双亲的编号为;否则结点i为根结点,无双亲。
(2)如果2i<=n,则结点i的右孩子的编号为2i;否则结点i无左孩子。
(3)如果2i+1<=n,则结点i的有孩子的编号为2i+1;否则结点i无右孩子。
3.二叉树的抽象数据类型定义
与树的定义基本一致,具体见后续代码
4.二叉树的遍历操作
- 前序遍历
- 中序遍历
- 后序遍历
- 层序遍历
前序序列、后序序列和中序序列可以唯一确定一棵二叉树,但是前序序列和后序序列不能唯一确定一棵二叉树。
具体的遍历在二叉树的存储结构及实现里以代码的形式展现
二、二叉树的存储结构及实现
1.顺序存储结构
用一维数组存储二叉树中的结点,并且用节点的存储位置(下标)表示结点之间的逻辑关系——父子关系。具体步骤如下:
(1)将二叉树按完全二叉树顺序编号。根节点的编号为1,若某结点i有左孩子,则其右孩子的编号为2i+1;
(2)将二叉树中的结点以编号顺序存储到一维数组中。注意,c++语言的数组下标从0开始,因此,编号为i的结点存储到下标为i-1的位置。
显然这种方法会造成存储空间的浪费,最坏的情况是右斜树。
2.二叉链表
二叉树一般采用二叉链表存储,其基本思想是:令二叉树的每个结点对应一个链表结点,链表结点除了存放与二叉树结点有关的数据信息外,还要设置左右孩子的指针。
结构:
struct BiNode
{
char data;
BiNode *lchild,*rchild;
};
data为数据域,存放该结点的数据信息;Ichild为左指针域,左孩子不存在即为空;Rchild为右指针域,右孩子不存在即为空;
1.前序遍历
void PreOrder(BiNode *bt)
{
if(bt==nullptr)return;
else
{
cout<<bt->data<<" ";
PreOrder(bt->lchild);
PreOrder(bt->rchild);
}
}
2.后序遍历
void PostOrder(BiNode *bt)
{
if(bt==nullptr)return;
else
{
PostOrder(bt->lchild);
PostOrder(bt->rchild);
cout<<bt->data<<" ";
}
}
3.中序遍历
void InOrder(BiNode *bt)
{
if(bt==nullptr)return;
else
{
InOrder(bt->lchild);
cout<<bt->data<<" ";
InOrder(bt->rchild);
}
}
4.层序遍历
#ifndef BiTree_H
#define BiTree_H
struct BiNode
{
char data;
BiNode *lchild,*rchild;
};
class BiTree
{
public:
BiTree(){root=Creat(root);}
~BiTree(){Release(root);}
void PreOrder(){PreOrder(root);}
void InOrder(){InOrder(root);}
void PostOrder(){PostOrder(root);}
int DepthOfTree(){DepthOfTree(root);}
int GetNodenum(){GetNodenum(root);}
void GetLeafNode(){GetLeafNode(root);}
void LeverOrder();
private:
BiNode *root;
BiNode *Creat(BiNode *bt);
void Release(BiNode *bt);
void PreOrder(BiNode *bt);
void InOrder(BiNode *bt);
void PostOrder(BiNode *bt);
int DepthOfTree(BiNode *bt);
int GetNodenum(BiNode *bt);
void GetLeafNode(BiNode *bt);
};
#endif // BiTree_H
#include <iostream>
using namespace std;
BiNode *BiTree::Creat(BiNode *bt)
{
char ch;
cin>>ch;
if(ch=='#') return NULL;
else
{
bt=new BiNode;
bt->data=ch;
bt->lchild=Creat(bt->lchild);
bt->rchild=Creat(bt->rchild);
}
return bt;
}
void BiTree::Release(BiNode *bt)
{
if(bt!=nullptr);
Release(bt->lchild);
Release(bt->rchild);
delete bt;
}
void BiTree::PreOrder(BiNode *bt)
{
if(bt==nullptr)return;
else
{
cout<<bt->data<<" ";
PreOrder(bt->lchild);
PreOrder(bt->rchild);
}
}
void BiTree::InOrder(BiNode *bt)
{
if(bt==nullptr)return;
else
{
InOrder(bt->lchild);
cout<<bt->data<<" ";
InOrder(bt->rchild);
}
}
void BiTree::PostOrder(BiNode *bt)
{
if(bt==nullptr)return;
else
{
PostOrder(bt->lchild);
PostOrder(bt->rchild);
cout<<bt->data<<" ";
}
}
void BiTree::LeverOrder()
{
int front,rear;
BiNode *Q[10000];
front=rear=-1;
if(root==NULL) return;
Q[++rear]=root;
BiNode *q;
while(front!=rear)
{
q=Q[++front];
cout<<q->data<<" ";
if(q->lchild!=NULL) Q[++rear]=q->lchild;
if(q->rchild!=NULL) Q[++rear]=q->rchild;
}
}
int BiTree::DepthOfTree(BiNode *bt)
{
if(bt==nullptr)return 0;
else return max(DepthOfTree(bt->lchild),DepthOfTree(bt->rchild))+1;
}
int BiTree::GetNodenum(BiNode *bt)
{
if(bt==nullptr)return 0;
else return GetNodenum(bt->lchild)+GetNodenum(bt->rchild)+1;
}
void BiTree::GetLeafNode(BiNode *bt)
{
if(bt==nullptr)return;
else
{
if(bt->lchild==nullptr&&bt->rchild==nullptr)
{
cout<<bt->data<<" ";
}
GetLeafNode(bt->lchild);
GetLeafNode(bt->rchild);
}
}
int main()
{
BiTree T;
cout<<"-----Preorder-----"<<endl;
T.PreOrder();
cout<<endl;
cout<<"-----Inorder-----"<<endl;
T.InOrder();
cout<<endl;
cout<<"-----Postorder-----"<<endl;
T.PostOrder();
cout<<endl;
cout<<"-----LeverOrder-----"<<endl;
T.LeverOrder();
cout<<endl;
cout<<"-----The number of leafnode-----"<<endl;
T.GetLeafNode();
cout<<endl;
cout<<"-----The depth of tree-----"<<endl;
cout<<T.DepthOfTree()<<endl;
cout<<"-----The number of tree's nodes-----"<<endl;
cout<<T.GetNodenum()<<endl;
}
5.构造函数
BiNode *Creat(BiNode *bt)
{
char ch;
cin>>ch;
if(ch=='#') return NULL;
else
{
bt=new BiNode;
bt->data=ch;
bt->lchild=Creat(bt->lchild);
bt->rchild=Creat(bt->rchild);
}
return bt;
}
6.析构函数
void Release(BiNode *bt)
{
if(bt!=nullptr);
Release(bt->lchild);
Release(bt->rchild);
delete bt;
}
完整代码:
3.三叉链表
能够借助二叉链表访问某结点的孩子结点,但是要找到它的双亲结点,则需要从根结点开始,最坏情况下,需要遍历整个二叉链表。此时,用三叉链表存储显得十分必要。
结构:
data、Ichild、rchild三个域的含义与二叉链表相同;parent域为指向结点的双亲结点的指针。
示例:
三叉链表既便于查找孩子,又便于查找双亲;但是增加了空间开销。
三、树、森林与二叉树的转换
1.树转换为二叉树
(1)加线——树中所有相邻兄弟之间加一条连线;
(2)去线——对树中的每个结点,只保留它与第一个孩子结点之间的连线,删去它与其他孩子节点之间的连线;
(3)层次调整——以树结点为轴心,将树顺时针转动一定角度,使之层次分明。
示例:
2.森林转换成二叉树
(1)将森林中每棵树转换成二叉树;
(2)从第二棵二叉树开始,依次把后一颗二叉树的根结点作为前一棵二叉树根结点的有孩子,当所有二叉树连起来后,此时所得到的二叉树就是森林转换的得到的二叉树。
示例:
3.二叉树转换成树或森林
(1)加线——若结点x是其双亲y的左孩子,则把结点x的有孩子、右孩子的右孩子、......,都与系结点y用线连起来;
(2)去线——删去原二叉树中所有的双亲结点与右孩子结点的连线;
(3)层次调整——整理(1)、(2)两步所得到的树或森林,使之层次分明。
示例:
总结
本篇仅仅记录了树的基本操作,有关线索链表以及树的遍历非递归算法之后会完善。