算法与数据结构-5.树和二叉树
5.1 树和二叉树的定义
5.1 1 树的定义
树是n(n≥0)个结点的有限值,当n=0时称为空树。
非空树:
(1)有且仅有一个称之为根的结点
(2)除根结点以外的其余结点可分为m个互不相交的有限集,其中每个集合本身又是一棵树,称为根的子树。
树的结构定义是一个递归的定义。
5.1.2 树的基本术语
- 结点:树中的一个独立单元,包含一个数据元素及若干指向其子树的分支。
- 结点的度:结点拥有的子树数。
- 树的度:树内各结点度的最大值。
- 叶子/终端结点:度为0的结点。
- 非终端结点/分支结点:度不为0的结点。除根结点之外,非终端结点也称为内部结点。
- 双亲和孩子:结点的子树的根称为该结点的孩子,该结点称为孩子的双亲。
- 兄弟:同一个双亲的孩子之间互称兄弟。
- 祖先:从根到该结点所经分支上的所有结点。
- 子孙:以某结点为根的子树中的任一结点都称为该结点的子孙。
- 层次:从根开始算起为第一层,树中任一结点层次等于其双亲结点的层次+1。
- 堂兄弟:双亲在同一层的互为堂兄弟。
- 树的深度/高度:树中结点的最大层次。
- 有序树和无序树:如果将树中结点的各子树看成从左至右是有次序的,则称为有序树,否则称为无序树。在有序树中最左边子树的根称为第一个孩子,最右边的称为最后一个孩子。
- 森林:是m个互不相交的树的集合。
- 满二叉树:深度为k且含有2k-1个结点的二叉树。
特点:每一层上的结点数都是最大结点数,即每一层i的结点数都具有最大值2i-1. - 完全二叉树:深度为k的,有n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树中标号从1至n的结点一一对应时,称为完全二叉树。
特点:(1)叶子结点只可能在层次最大的两层上出现。
(2)对任一结点,若其右分支下的子孙的最大层次为l,则其左分支下的子孙的最大层次必为l或l+1。
5.1.3 二叉树的定义
二叉树是n(n≥0)个结点所构成的合集。
非空二叉树T:
(1)有且仅有一个称之为根的结点
(2)除根结点以外的其余结点分为两个互不相交的子集T1和T2,分别称为T的左子树和右子树,且T1和T2本身都是二叉树。
二叉树与树的主要区别:
(1)二叉树的每个结点至多只有两棵子树,即不存在度大于2的结点。
(2)二叉树的子树有左右之分,次序不能任意颠倒。
其中,二叉树有五种基本形态。
5.1.4 线索二叉树
在第一次遍历时将结点的前驱、后继信息存储下来,便于再次遍历二叉树。
线索化:若无左子树,则其左指针指向其前驱结点。若无右子树,则其左指针指向其后继结点。
5.1.5 二叉树的性质
- 在二叉树的第i层上至多有2(i-1)个结点。(i≥1)
- 深度为k的二叉树至多有2k-1个结点。(k≥1)
- 对任何一颗二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则n0=n2+1。
- 具有n个结点的完全二叉树的深度为|log2n|+1。
- 如果对一颗有n个结点的完全二叉树(其深度为|log2n|+1)的结点按层序编号(从第1层到第|log2n|+1层,每层从左到右),则对任一结点i(1≤i≤n),有
(1)如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则双亲parent(i)是结点i/2。
(2)如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子lchild(i)是结点2i。
(3)如果2i+1>n,则结点i无右孩子;否则其右孩子rchild(i)是结点2i+1。
二叉树的顺序存储结构
顺序存储结构使用一组地址连续的存储单元来存储数据元素。结点按照一定的规律安排在这组单元中。
对于完全二叉树,从根起按层序存储,依次自上而下、自左至右存储结点元素。(适用)
对于一般二叉树,则应将其每个结点与完全二叉树上的结点相对照,可以“0”表示不存在此结点。
顺序存储结构的前序遍历
#include<iostream>
#include<cstring>
using namespace std;
char str[1000];
void bianli(int i,int n){
if(str[i]!='#')
cout<<str[i];
if(2*i<=n)
bianli(2*i,n);
if(2*i+1<=n)
bianli(2*i+1,n);
}
int main(){
int l,n,i;
while(cin>>str+1){
l=strlen(str+1);
bianli(1,l);
cout<<endl;
}
return 0;
}
顺序存储转换为链式存储
#include<iostream>
#include<cstring>
using namespace std;
char s[55];
typedef struct BiLnode{
char data;
struct BiLnode *lchild,*rchild;
}BiLnode,*BiTree;
BiTree create(int i,int l){
BiTree t=new BiLnode;
t->data=s[i];
t->lchild=t->rchild=NULL;
if(2*i<=l)
t->lchild=create(2*i,l);
if(2*i+1<=l)
t->rchild=create(2*i+1,l);
return t;
}
void print(BiTree t){
if(t){
if(t->data!='#')
cout<<t->data;
print(t->lchild);
print(t->rchild);
}
}
int main(){
int n,l;
cin>>n;
BiTree t;
while(n--){
cin>>s+1; //从第二位开始输入
l=strlen(s+1);
if(s[1]=='#')
continue;
t=create(1,l);
print(t);
cout<<endl;
}
return 0;
}
5.1.6二叉树的链式存储结构
设计不同的结点结构可构成不同形式的链式存储结构。如二叉链表,三叉链表。
在含有n个结点的二叉链表中有n+1个空链域。
利用空链域存储其他有用信息,可得到另一种链式存储结构——线索链表。
二叉链表存储表示
typedef struct BiTLnode{
char data;
struct BiTLnode *lchild,*rchild;
}BiTLnode,*BiTree;
遍历二叉树
先序遍历:根—左—右
中序遍历:左—根—右
后序遍历:左—右—根
层序遍历:从上到下,从左至右
无论是哪种遍历方式,其时间复杂度均为O(n),空间复杂度也为O(n)。
确定二叉树:前+中、后+中
中序遍历
递归算法:
void InOrder(BiTree t){
if(t){
InOrder(t->lchild);
cout<<t->data;
InOrder(t->rchild);
}
}
非递归算法:
void InOrder(BiTree t){
BiTLnode *p,*q
InitStack(s);
p=t;
q=new BiTLnode;
while(p||!empty(s)){
if(p){
push(s,p);
p=p->lchild;
}else{
pop(s,q);
cout<<q->data;
p=q->rchild;
}
}
}
层序遍历
void CengXuBianli(BiTree T){
queue<BiLnode *>p;
p.push(T);
while(!p.empty()){
if(p.front()->lchild!=NULL)
p.push(p.front()->lchild);
if(p.front()->rchild!=NULL)
p.push(p.front()->rchild);
cout<<p.front()->data;
p.pop();
}
}
先序遍历创建二叉链表
void Create(BiTree t){
char x;
cin>>x;
if(x=='#')
t=NULL;
else{
t=new Lnode;
t->data=x;
Create(t->lchild);
Create(t->rchild);
}
}
复制二叉树
void copy(BiTree t,BiTree &newt){
if(t==NULL){
newt=NULL;
return;
}else{
newt=new BiTLnode;
newt->data=t->data;
copy(t->lchild,newt->lchild);
copy(t->rchild,newt->rchild);
}
}
计算二叉树的深度
int depth(BiTree t){
int ldepth,rdepth;
if(t==NULL)
return 0;
else{
ldepth=depth(t->lchild);
depth=depth(t->rchild);
if(ldepth>rdepth)
return ldepth+1;
else
return rdepth+1;
}
}
统计二叉树中结点的个数
int nodeCount(BiTree t){
if(t==NULL)
return 0;
else{
return nodeCount(t->lchild)+nodeCount(t->rchild)+1;
}
}
统计二叉树中叶结点的个数
int findYe(BiTree T){
int num1,num2;
if(T==NULL)
return 0;
else if(T->lchild==NULL&&T->rchild==NULL) {
return 1;
}
else{
num1= findYe(T->lchild);
num2= findYe(T->rchild);
}
return (num1+num2);
}
统计二叉树中度为1的结点个数
统计二叉树中度为2的结点个数
int findDu2(BiTree t){
int num=0;
if(t==NULL)
return 0;
else if(t->lchild!=NULL&&t->rchild!=NULL){
num++;
return findDu2(t->rchild)+findDu2(t->lchild)+1;
}else
return 0;
}
求中序遍历序列的第一个结点值
BiLnode* zhong(BiTree t){
if(t==NULL)
return NULL;
while(t->lchild!=NULL)
t=t->lchild;
return t;
}
输出二叉树
void print(LinkList l){
Lnode *p;
p=l->next;
while(p){
cout<<p->data;
p=p->next;
}
cout<<endl;
}
用括号法输出二叉树
void print(BiTree t){
if(t){
cout<<t->data;
if(t->lchild!=NULL||t->rchild!=NULL){
cout<<"(";
print(t->lchild);
if(t->rchild!=NULL){
cout<<",";
}
print(t->rchild);
cout<<")";
}
}
}
二叉树的按值查找
BiLnode *findNode(BiTree t,char x){
BiLnode *p;
p=new BiLnode;
p=NULL;
if(t){
if(t->data==x)
return t;
if(t->lchild!=NULL){
p=findNode(t->lchild,x);
if(p)
return p;
}
if(t->rchild!=NULL){
p=findNode(t->rchild,x);
if(p)
return p;
}
}
return NULL;
}
二叉链表查找其双亲及左右孩子值
void find(BiTree &t,char k){
if(t)
{
if(t->data==x)
{
flag=1;
cout<<k<<"(";
if(t->lchild!=NULL)
cout<<t->lchild->data<<",";
else
cout<<"#,";
if(t->rchild!=NULL)
cout<<t->rchild->data<<")";
else
cout<<"#)";
return ;
}
find(t->lchild,t->data);
find(t->rchild,t->data);
}
}
二叉树的删除以值x为根结点的子树
void find(BiTree &t,char x){
if(t)
{
if(t->data==x)
t=NULL;
else
{
find(t->lchild,x);
find(t->rchild,x);
}
}
}
5.2 树和森林
5.2.1树的存储结构
- 双亲表示法
- 孩子表示法
- 孩子兄弟法
5.2.2 森林与二叉树的转换
任何一颗和树对应的二叉树,其根结点的右子树必空。
见书P135。
- 森林转换成二叉树
- 二叉树转换成森林
5.3 哈夫曼树
哈夫曼树又称最优树。
在哈夫曼树中,任何一个结点它的度都是0或2。
哈夫曼树的结点个数不能是偶数。
- 路径:从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径。
- 路径长度:路径上的分支数目。
- 树的路径长度:从树根到每一结点的路径长度之和。
- 权:赋予某个实体的一个量。
- 结点的带权路径长度:从该结点到树根之间的路径长度与结点上权的乘积。
- 树的带权路径长度:树中所有叶子结点的带权路径长度之和,记作WPL。
- 哈夫曼树:WPL最小的二叉树。
哈夫曼编码是最优前缀编码。
哈夫曼树的根结点的权值等于各个叶子结点的权值之和。
当一棵具有n个叶子结点的二叉树的WPL值为最小时,称其树为哈夫曼树,其二叉树的形状是唯一的。
哈夫曼树是带权路径长度最短的树,路径上权值较大的结点离根较近。
※易错点
- 在二叉树中,第i层的i结点个数最多为2i-1个。
- 第i层的最大结点数是第i-1层最大结点数的两倍。
- 深度为k的二叉树最多有2k-1个结点。
- 叶子结点数为度数为2的结点数+1。
- 具有n个结点的完全二叉树的深度为|log2n|+1。
- 在含有n个结点的二叉链表中含有n+1个空链域。
- 任何一棵和树对应的二叉树,其根结点的右子树必空。
- 哈夫曼树中只有度数为0或2的结点,不存在度数为1的结点。
- 一棵有n个叶子结点的哈夫曼树共有2n-1的结点。
- 哈夫曼树的结点个数不能是偶数。
- 存储数据时,通常不仅要存储数据元素的值,还要存储元素之间的关系。
- 数据结构的实质就是相互存在各种特定关系的数据元素的集合。
- 数据元素是数据的基本单位。根据数据元素之间的关系的不同特征,可以分成集合、线性结构、树状结构、图状或者网状结构4类基本结构。
- 如果无向完全图G中有28条边(边数为n(n-1)/2),则G的生成树有7条边。
树、二叉树、森林的遍历
- 树→二叉树:
- 森林→二叉树:
- 二叉树→树: