一、树

1.概述

树(Tree)是n(n≥0)个结点的有限集合,当n=0时,为空树;n>0时,为非空树。任意一棵非空树,满足:

1)有且仅有一个称之为根的结点;

2)除根结点以外的其余结点可分为m(m>0)个互不相交的有限集T1, T2, …, Tm, 其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)。

树和二叉树_树结构

2.树的相关术语

● 节点——节点包含数据元素及若干指向子树的分支信息。
● 节点的度——节点拥有的子树个数。
● 树的度——树中节点的最大度数。
● 终端节点——度为0的节点,又称为叶子
● 分支节点——度大于0的节点。除了叶子都是分支节点。
● 内部节点——除了树根和叶子都是内部节点。
● 节点的层次——从根到该节点的层数(根节点为第1层)。
● 树的深度(或高度)——指所有节点中最大的层数。
● 路径——树中两个节点之间所经过的节点序列。
● 路径长度——两节点之间路径上经过的边数。
● 双亲、孩子——节点的子树的根称为该节点的孩子,反之,该节点为其孩子的双亲。
● 兄弟——双亲相同的节点互称兄弟。
● 堂兄弟——双亲是兄弟的节点互称堂兄弟。
● 祖先——从该节点到树根经过的所有节点称为该节点的祖先。
● 子孙——节点的子树中的所有节点都称为该节点的子孙。
● 森林——由m(m≥0)棵不相交的树组成的集合。

3.树的存储结构

顺序存储

分为双亲表示法、孩子表示法和双亲孩子表示法。

树和二叉树_树结构_02


3种表示法的优缺点如下。

双亲表示法只记录了每个节点的双亲,无法直接得到该节点的孩子;孩子表示法可以得到该节点的孩子,但是无法直接得到该节点的双亲,而且由于不知道每个节点到底有多少个孩子,因此只能按照树的度(树中节点的最大度)分配孩子空间,这样做可能会浪费很多空间。双亲孩子表示法是在孩子表示法的基础上,增加了一个双亲域,可以快速得到节点的双亲和孩子,其缺点和孩子表示法一样,可能浪费很多空间。

链式存储

(1)孩子链表表示法

类似于邻接表,表头包含数据元素并指向第一个孩子指针,将所有孩子放入一个单链表中。在表头中,data存储数据元素,first为指向第1个孩子的指针。单链表中的节点记录该节点的下标和下一个节点的地址。仍以图6-10为例,其孩子链表表示法如图6-12所示。

树和二叉树_子树_03

(2)孩子兄弟表示法

节点除了存储数据元素之外,还有两个指针域lchild和rchild,被称为二叉链表。lchild存储第一个孩子地址,rchild存储右兄弟地址。其节点的数据结构如图6-13所示。

树和二叉树_子树_04


树如图所示:

树和二叉树_树结构_05

4.树、森林与二叉树的转换

(1).树和二叉树的转换

秘诀:长子当作左孩子,兄弟关系向右

树和二叉树_子树_06


还原时逆操作即可.

(2).森林和二叉树的转换

可以把森林中的每棵树的树根看作兄弟关系,因此3棵树的树根B、C和D是兄弟,兄弟关系在右斜线上,其他的转换和树转二叉树一样,长子当作左孩子,兄弟关系向右斜。或者把森林中的每一棵树转换成二叉树,然后把每棵树的根节点连接在右斜线上即可

树和二叉树_子树_07


以上大部分内容来自陈小玉老师的《趣学数据结构》

二、二叉树

1.概述

二叉树(binary tree)是n(n≥0)个节点构成的集合,
它或为空树(n=0),或满足以下两个条件:
1)有且仅有一个称为根的节点;
2)除根节点以外,其余节点分为两个互不相交的子集T1和T2,分别称为T的左子树和右子树,且T1和T2本身都是二叉树。

二叉树是一种特殊的树,它最多有两个子树,分别为左子树和右子树,二者是有序的,不可以互换。也就是说,二叉树中不存在度大于2的节点。

两种特殊的二叉树:
●满二叉树:一棵深度为k且有2k-1个节点的二叉树。满二叉树每一层都“充满”了节点,达到最大节点数,如图6-24所示。
●完全二叉树:除了最后一层外,每一层都是满的(达到最大节点数),最后一层节点是从左向右出现的

2.性质

性质1:在二叉树的第i层上至多有 2(i-1) 个节点。
性质2:深度为k的二叉树至多有 2^k-1个节点。
性质3:对于任何一棵二叉树,若叶子数为n0,度为2的节点数为n2,则n0=n2+1。
性质4:具有n个节点的完全二叉树的深度必为 ​​​log2n​​​+1。
性质5:对于完全二叉树,若从上至下、从左至右编号,则编号为i的节点,其左孩子编号必为2i,其右孩子编号必为2i +1,其双亲的编号必为i/2。

3. 二叉树的存储结构

1.顺序存储(可以使用树存储的三种方法,也可使用两个数组,一个记录左孩子,一个记录右孩子,节点和索引相对应)
2. 链式存储:二叉链表(最常用的那个,一个存放数据,还有两个指针),三叉链表

4.二叉树的创建

1.询问法
void createtree(Btree &T){
char check;
T = new Bnode;
cout<<"请输入节点信息:"<<endl;
cin>>T->data;
cout<<"是否添加"<<T->data<<"的左孩子? (Y/N)"<<endl;//询问是否创建T的左子树
cin>>check;
if(check=='Y'){
createtree(T->lchild);
} else{
T->lchild = NULL;
}
cout<<"是否添加"<<T->data<<"的右孩子? (Y/N)"<<endl;//询问是否创建T的左子树
cin>>check;
if(check=='Y'){
createtree(T->rchild);
} else{
T->rchild = NULL;
}
}
2.补空法(更常用)
void Createtree(Btree &T) {  /*创建二叉树函数*/
char ch;
cin>>ch;
if(ch=='#'){
T = NULL;
} else{
T = new Bnode;
T->data = ch;
Createtree(T->lchild);
Createtree(T->rchild);
}
}

三、二叉树的遍历

直接上代码

#include<iostream>
#include<queue>
using namespace std;
typedef struct Bnode {
char data;
Bnode *lchild,*rchild;
} Bnode,*Btree;//Btree指代二叉树

void Createtree(Btree &T) { /*创建二叉树函数*/
char ch;
cin>>ch;
if(ch=='#'){
T = NULL;
} else{
T = new Bnode;
T->data = ch;
Createtree(T->lchild);
Createtree(T->rchild);
}
}

void preorder(Btree T){//先序遍历
if(T){
cout<<T->data<<" ";
preorder(T->lchild);
preorder(T->rchild);
}
}

void inorder(Btree T){//中序遍历
if(T){
preorder(T->lchild);
cout<<T->data<<" ";
preorder(T->rchild);
}
}

void posorder(Btree T){//后序遍历
if(T){
posorder(T->lchild);
preorder(T->rchild);
cout<<T->data<<" ";
}
}

bool Leveltraverse(Btree T){
Btree p;
if(!T){//如果是空树则结束
return false;
}
queue<Btree> Q;
Q.push(T);
while(!Q.empty()){
p=Q.front();
Q.pop();
cout<<p->data<<" ";
if(p->lchild){
Q.push(p->lchild);
}
if(p->rchild){
Q.push(p->rchild);
}
}
return true;
}

int main() {
Btree mytree;
cout<<"按先序次序输入二叉树中结点的值(孩子为空时输入#),创建一棵二叉树"<<endl;
Createtree(mytree);//创建二叉树
cout<<endl;
cout<<"二叉树的先序遍历结果:"<<endl;
preorder(mytree);//先序遍历二叉树
cout<<endl;
cout<<"二叉树的中序遍历结果:"<<endl;
inorder(mytree);//中序遍历二叉树
cout<<endl;
cout<<"二叉树的后序遍历结果:"<<endl;
posorder(mytree);//后序遍历二叉树
cout<<endl;
cout<<"二叉树的层次遍历结果:"<<endl;
Leveltraverse(mytree);//层次遍历二叉树
return 0;
}

运行结果:

树和二叉树_子树_08


测试的二叉树:

树和二叉树_二叉树_09

四、树的应用

1.前序/后序和中序还原建立二叉树.

#include <iostream>
using namespace std;
typedef struct node
{
char data;
struct node *lchild,*rchild;
}BiTNode,*BiTree;

BiTree pre_mid_createBiTree(char *pre,char *mid,int len) //前序中序还原建立二叉树
{
if(len==0)
return NULL;
char ch=pre[0]; //找到先序中的第一个结点
int index=0;
while(mid[index]!=ch)//在中序中找到的根结点的左边为该结点的左子树,右边为右子树
{
index++;
}
BiTree T=new BiTNode;//创建根结点
T->data=ch;
T->lchild=pre_mid_createBiTree(pre+1,mid,index);//建立左子树
T->rchild=pre_mid_createBiTree(pre+index+1,mid+index+1,len-index-1);//建立右子树
return T;
}

BiTree pro_mid_createBiTree(char *last,char *mid,int len)//后序中序还原建立二叉树
{
if(len==0)
return NULL;
char ch=last[len-1]; //取得后序遍历顺序中最后一个结点
int index=0;//在中序序列中找根结点,并用index记录长度
while(mid[index]!=ch)//在中序中找到根结点,左边为该结点的左子树,右边为右子树
index++;
BiTree T=new BiTNode;//创建根结点
T->data=ch;
T->lchild=pro_mid_createBiTree(last,mid,index);//建立左子树
T->rchild=pro_mid_createBiTree(last+index,mid+index+1,len-index-1);//建立右子树
return T;
}

void pre_order(BiTree T)//前序递归遍历二叉树
{
if(T)
{
cout<<T->data;
pre_order(T->lchild);
pre_order(T->rchild);
}
}

void pro_order(BiTree T)//后序递归遍历二叉树
{
if(T)
{
pro_order(T->lchild);
pro_order(T->rchild);
cout<<T->data;
}
}
int main()
{
BiTree T;
int n;
char pre[100],mid[100],last[100];
cout<<"1. 前序中序还原二叉树\n";
cout<<"2. 后序中序还原二叉树\n";
cout<<"0. 退出\n";
int choose=-1;
while(choose!=0)
{
cout<<"请选择:";
cin>>choose;

switch (choose)
{
case 1://前序中序还原二叉树
cout<<"请输入结点的个数:"<<endl;
cin>>n;
cout<<"请输入前序序列:"<<endl;
for(int i=0;i<n;i++)
cin>>pre[i];
cout<<"请输入中序序列:"<<endl;
for(int i=0;i<n;i++)
cin>>mid[i];
T=pre_mid_createBiTree(pre,mid,n);
cout<<endl;
cout<<"二叉树还原成功,输出其后序序列:"<<endl;
pro_order(T);
cout<<endl<<endl;
break;
case 2://后序中序还原二叉树
cout<<"请输入结点的个数:"<<endl;
cin>>n;
cout<<"请输入后序序列:"<<endl;
for(int i=0 ;i<n;i++)
cin>>last[i];
cout<<"请输入中序序列:"<<endl;
for(int i=0 ;i<n;i++)
cin>>mid[i];
T=pro_mid_createBiTree(last,mid,n);
cout<<endl;
cout<<"二叉树还原成功,输出其先序序列:"<<endl;
pre_order(T);
cout<<endl<<endl;
break;
}
}
return 0;
}

运行结果

树和二叉树_结点_10


树和二叉树_二叉树_11

2.求叶子数和节点数

#include <iostream>
using namespace std;

typedef struct Bnode /*定义二叉树存储结构*/
{ char data;
struct Bnode *lchild,*rchild;
}Bnode,*Btree;

void Createtree(Btree &T) /*创建二叉树函数*/
{
//按先序次序输入二叉树中结点的值(一个字符),创建二叉链表表示的二叉树T
char ch;
cin >> ch;
if(ch=='#')
T=NULL; //递归结束,建空树
else{
T=new Bnode;
T->data=ch; //生成根结点
Createtree(T->lchild); //递归创建左子树
Createtree(T->rchild); //递归创建右子树
}
}

int LeafCount(Btree T)//求二叉树的叶子数
{
if(T==NULL)//如果为空树,深度为0
return 0;
else
if(T->lchild==NULL&&T->rchild==NULL)//左右子树均为空,则叶子数为1
return 1;
else
return LeafCount(T->lchild)+LeafCount(T->rchild);//递归计算左子树和右子树的叶子数之和
}

int NodeCount(Btree T)//求二叉树的结点数
{
if(T==NULL)//如果为空树,深度为0
return 0;
else
return NodeCount(T->lchild)+NodeCount(T->rchild)+1;//递归计算左子树和右子树的结点数之和加1
}

int main()
{
Btree mytree;
cout<<"按先序次序输入二叉树中结点的值(孩子为空时输入#),创建一棵二叉树"<<endl;
//ABD##E##CF#G###
Createtree(mytree);//创建二叉树
cout<<endl;
cout<<"二叉树的结点数为:"<<NodeCount(mytree)<<endl;
cout<<"二叉树的叶子数为:"<<LeafCount(mytree)<<endl;
return 0;
}

3.求二叉树的深度

#include <iostream>
using namespace std;

typedef struct Bnode /*定义二叉树存储结构*/
{ char data;
struct Bnode *lchild,*rchild;
}Bnode,*Btree;

void Createtree(Btree &T) /*创建二叉树函数*/
{
//按先序次序输入二叉树中结点的值(一个字符),创建二叉链表表示的二叉树T
char ch;
cin >> ch;
if(ch=='#')
T=NULL; //递归结束,建空树
else{
T=new Bnode;
T->data=ch; //生成根结点
Createtree(T->lchild); //递归创建左子树
Createtree(T->rchild); //递归创建右子树
}
}

int Depth(Btree T)//求二叉树的深度
{
int m,n;
if(T==NULL)//如果为空树,深度为0
return 0;
else
{
m=Depth(T->lchild);//递归计算左子树深度
n=Depth(T->rchild);//递归计算左子树深度
if(m>n)
return m+1;//返回左右子树最大值加1
else
return n+1;
}
}

int main()
{
Btree mytree;
cout<<"按先序次序输入二叉树中结点的值(孩子为空时输入#),创建一棵二叉树"<<endl;
//ABD##E##CF#G###
Createtree(mytree);//创建二叉树
cout<<endl;
cout<<"二叉树的深度为:"<<Depth(mytree)<<endl;
return 0;
}