数据结构之认识树和二叉树

一.树的定义
从数据结构角度看,树包含n(n≥0)个结点,当n=0时,称为空树;非空树的定义如下: T=(D,R)
其中,D为树中结点的有限集合,关系R满足以下条件:
●有且仅有一个结点d0∈D,它对于关系R来说没有前驱结点,结点d0称作树的根结点
●除根结点d0外,D中的每个结点有且仅有一个前驱结点,但可以有多个后继结点
●D中可以有多个终端结点

假如我们有一棵树T=(D,R),其中:D={A,B,C,D,E,F,G,H},R={r}
r={<A,B>,<A,C>,<A,D>,<C,E>,<C,F>,<D,G>,<E,H>},那我们该如何画出其逻辑结构图呢?

解:A是根结点,其余结点分成三个互不相交的子集:  T1={B},T2={C,E,F,H},T3={D,G}; T1、T2、T3都是根结点A的子树,且各自本身也是一棵树

于是我们得到了逻辑结构图,如下:
在这里插入图片描述
说明:树中结点之间的关系应为有向关系,在上图中,结点之间的连线即分支线都是有向的,默认箭头向下的。

二.树的逻辑结构表示
1.树形表示法:使用一棵倒置的树表示树结构,非常直观和形象
2.文氏图表示法:使用集合以及集合的包含关系描述树结构,如图:
在这里插入图片描述
3.凹入表示法:使用线段的伸缩关系描述树结构
在这里插入图片描述
4.括号表示法:将树的根结点写在括号的左边,除根结点之外的其余结点写在括号中并用逗号分隔
在这里插入图片描述
三.树的基本术语
1.什么是结点的度?

树中每个结点具有的子树数或者后继结点数称为该结点的度

在这里插入图片描述
2.什么是树的度?

树中所有结点的度的最大值称之为树的度

在这里插入图片描述
3.什么是分支结点?

度大于0的结点称为分支结点或非终端结点。 度为1的结点称为单分支结点,度为2的结点称为双分支结点,依次类推…

在这里插入图片描述
4.什么是叶子节点?

度为零的结点称为叶子结点或终端结点

在这里插入图片描述
5.什么是孩子结点?

一个结点的后继称之为该结点的孩子结点

在这里插入图片描述
6.什么是双亲结点?

一个结点称为其后继结点的双亲结点

在这里插入图片描述
7.什么是子孙结点?

一个结点的子树中除该结点外的所有结点称之为该结点的子孙结点

在这里插入图片描述
8.什么是祖先结点?

从树根结点到达某个结点的路径上通过的所有结点称为该结点的祖先结点(不含该结点自身)

在这里插入图片描述
9.什么是兄弟结点?

具有同一双亲的结点互相称之为兄弟结点

在这里插入图片描述
10.什么是结点层次?

树具有一种层次结构,根结点为第一层,其孩子结点为第二层,如此类推得到每个结点的层次

在这里插入图片描述
11.什么是树的高度?

树中结点的最大层次称为树的高度或深度

在这里插入图片描述
12.什么是森林?

零棵或多棵互不相交的树的集合称为森林

在这里插入图片描述
四.树的性质
性质1: 树中的结点数等于所有结点的度数加1

性质2:度为m的树中第i层上至多有m^(i-1)个结点,这里应有i≥1

推广:当一棵m次树的第i层有m^(i-1)个结点(i≥1)时,称该层是满的,若一棵m次树的所有叶子结点在同一层,除该层外其余每一层都是满的,称为满m次树。显然,满m次树是所有相同高度的m次树中结点总数最多的树。也可以说,对于n个结点,构造的m次树为满m次树或者接近满m次树,此时树的高度最小。

让我们来道经典例题巩固一下:
若一棵三次树中度为3的结点为2个,度为2的结点为1个,度为1的结点为2个,则该三次树中总的结点个数和度为0的结点个数分别是多少?

解:设该三次树中总结点个数、度为0的结点个数、度为1的结点个数、度为2的结点个数和度为3的结点个数分别为n、n0、n1、n2和n3。
显然,每个度为i的结点在所有结点的度数之和中贡献i个度。依题意有:n1=2,n2=1,n3=2。
由树的性质1可知:n = 所有结点的度数之和+1 
= 0×n0+1×n1+2×n2+3×n3+1
= 1×2+2×1+3×2+1
=11   
又因为n=n0+n1+n2+n3   
即:n0=n-n1-n2-n3=11-2-1-2=6   
所以该三次树中总的结点个数和度为0的结点个数分别是11和6。

五.二叉树
1.二叉树的递归定义
二叉树或者是一棵空树,或者是一棵由一个根结点和两棵互不相交的分别称做根结点的左子树和右子树所组成的非空树,左子树和右子树又同样都是一棵二叉树。

注意:二叉树与度为2的树是不同的!
度为2的树至少有3个结点,而二叉树的结点数可以为0;
度为2的树不区分子树的次序,而二叉树中的每个结点最多有两个孩子结点,且必须要区分左右子树,即使在结点只有一棵子树的情况下也要明确指出该子树是左子树还是右子树

2.二叉树的5种形态
在这里插入图片描述
3.二叉树的性质
性质1:非空二叉树上叶结点数等于双分支结点数加1,即n0=n2+1
(我们约定:二叉树上叶结点数为n0,单分支结点数为n1,双分支结点数为n2)

例如:一棵二叉树中总结点个数为200,其中单分支结点个数为19,求其叶子结点个数。
解:n=200,n1=19。又n=n0+n1+n2,由性质1得,n2=n0-1,所以有: n=2n0-1+n1
即n0=(n-n1+1)/2=91,所以这样的二叉树中叶子结点个数为91。

性质2:非空二叉树上第i层上至多有2^(i-1)个结点,这里应有i≥1

性质3:高度为h的二叉树至多有2^h-1个结点(h≥1)

性质4:对完全二叉树中编号为i的结点(1≤i≤n,n≥1,n为结点数)有:
(1)若i≤|n/2|,即2i≤n,则编号为i的结点为分支结点,否则为叶子结点;
(2)若n为奇数,则每个分支结点都既有左孩子结点,也有右孩子结点。若n为偶数,则编号最大的分支结点只有左孩子结点,没有右孩子结点,其余分支结点都有左、右孩子结点。
(3)若编号为i的结点有左孩子结点,则左孩子结点的编号为2i;若编号为i的结点有右孩子结点,则右孩子结点的编号为(2i+1)。
(4)除树根结点外,若一个结点的编号为i,则它的双亲结点的编号为|i/2|,也就是说,当i为偶数时,其双亲结点的编号为i/2,它是双亲结点的左孩子结点,当i为奇数时,其双亲结点的编号为(i-1)/2,它是双亲结点的右孩子结点。

4.满二叉树
在一棵二叉树中,当第i层的结点数为2^(i-1)个时,则称此层的结点数是满的,当一棵二叉树中的每一层都满时,且叶子结点在同一层上,则称此树为满二叉树。
在这里插入图片描述

满二叉树特性:除叶子结点以外的其他结点的度皆为2。

5.完全二叉树
在一棵二叉树中,除最后一层外,若其余层都是满的,并且最后一层或者是满的,或者是在右边缺少连续若干个结点,则称此树为完全二叉树。
在这里插入图片描述

完全二叉树特性:二叉树中至多只有最下边两层结点的度数小于2。

6.层序编号
从根结点为1开始,按照层次从小到大、同一层从左到右的次序顺序编号。如图:
在这里插入图片描述
六.二叉树的遍历
二叉树的遍历运算是指按一定的次序访问树中的所有结点,使每个结点恰好被访问一次。其中遍历次序保证了二叉树上每个结点均被访问一次且仅有一次。

1.先序遍历
① 访问根结点
② 先序遍历左子树
③ 先序遍历右子树

2.中序遍历
① 中序遍历左子树
② 访问根结点
③ 中序遍历右子树

3.后序遍历
① 后序遍历左子树
② 后序遍历右子树
③ 访问根结点

4.层次遍历
层次遍历是从根结点出发,按照从上向下,同一层从左向右的次序访问所有的结点,采用层次遍历得到的访问结点序列称为层次遍历序列。
层次遍历序列的特点:其第一个元素值为二叉树中根结点值。

七.二叉树的存储结构
1.顺序存储结构
在这里插入图片描述
用一组连续的存储单元存放二叉树中的结点
在这里插入图片描述
在这里插入图片描述
用“#”补齐为一个完整二叉树
在这里插入图片描述
2.二叉链存储结构
链表中的每个结点包含两个指针,分别指向对应结点的左孩子和右孩子(注意在树的孩子兄弟链表存储结构中,每个结点的两个指针分别指向对应结点的第一个孩子和下一个兄弟)

typedef struct tnode
{  ElemType data;			//数据域
   struct tnode *lchild,*rchild;	//指针域
} BTNode;

●data表示数据域,用于存储放入结点值(默认情况下为单个字母)
●lchild 和 rchild 分别表示左指针域和右指针域,分别存储左孩子和右孩子结点(即左、右子树的根结点)的存储地址
●当某结点不存在左或右孩子时,其 lchild 或 rchild 指针域取特殊值NULL
在这里插入图片描述
八.二叉树基本运算实现算法
采用括号表示法表示的二叉树字符串str,且每个结点的值是单个字符。
用ch扫描str,其中只有4类字符,各类字符的处理方式:
1.若ch=‘(’:表示前面刚创建的p结点存在着孩子结点,需将其进栈,以便建立它和其孩子结点的关系。然后开始处理该结点的左孩子,因此置k=1,表示其后创建的结点将作为这个结点(栈顶结点)的左孩子结点;

2.若ch=‘)’:表示以栈顶结点为根结点的子树创建完毕,将其退栈;

3.若ch=‘,’:表示开始处理栈顶结点的右孩子结点,置k=2;

4.其他情况:只能是单个字符,表示要创建一个新结点p,根据k值建立p结点与栈顶结点之间的联系,当k=1时,表示p结点作为栈顶结点的左孩子结点,当k=2时,表示p结点作为栈顶结点的右孩子结点。

(1)创建二叉树

void CreateBTree(BTNode * &bt,char *str)
{  BTNode *St[MaxSize],*p=NULL;
   int top=-1,k,j=0;
   char ch;
   bt=NULL;		//建立的二叉树初始时为空
   ch=str[j];
   while (ch!='\0')			    //str未扫描完时循环
   {	switch(ch)
	{
	case '(':top++;St[top]=p;k=1; break;    //为左孩子结点
	case ')':top--;break;
	case ',':k=2; break;		     //为右孩子结点
	default:p=(BTNode *)malloc(sizeof(BTNode));
		p->data=ch;p->lchild=p->rchild=NULL;
		if (bt==NULL)		     //p为二叉树的根结点
		  bt=p;
		else			     //已建立二叉树根结点
		{   switch(k) 
		  {
		  case 1:St[top]->lchild=p;break;
		  case 2:St[top]->rchild=p;break;
		  }
		}
      }
      j++; ch=str[j];
   }
}

(2)销毁二叉树

void DestroyBTree(BTNode *&bt)
{   if (bt!=NULL)
  {	
  	DestroyBTree(bt->lchild);
		DestroyBTree(bt->rchild);
        free(bt);
  }
}

(3)求二叉树高度

int BTHeight(BTNode *bt) 
{  
	int lchilddep,rchilddep;
    if (bt==NULL) return(0); 		   //空树的高度为0
    else
   {  
		lchilddep=BTHeight(bt->lchild);  //求左子树的高度
		rchilddep=BTHeight(bt->rchild);  //求右子树的高度
		return (lchilddep>rchilddep)? (lchilddep+1):(rchilddep+1);
   }
}

(4)求二叉树结点个数

int NodeCount(BTNode *bt)		//求二叉树bt的结点个数
{  
	int num1,num2;
	if (bt==NULL)			//为空树时返回0
	return 0;
	else
  	 {   
		num1=NodeCount(bt->lchild);	//求左子树结点个数
		num2=NodeCount(bt->rchild);	//求右子树结点个数
		return (num1+num2+1);		//返回和加上1
     }
}

(5)求二叉树叶子结点个数

int LeafCount(BTNode *bt)	//求二叉树bt的叶子结点个数
{  
	int num1,num2;
	if (bt==NULL)		//空树返回0
	return 0;
	else if (bt->lchild==NULL && bt->rchild==NULL) 
	return 1;		//为叶子结点时返回1
	else
	 {	
		num1=LeafCount(bt->lchild);	//求左子树叶子结点个数
		num2=LeafCount(bt->rchild); 	//求右子树叶子结点个数
		return (num1+num2);		//返回和
   }
}

(6)以括号表示法输出二叉树
1.对于非空二叉树bt,先输出其元素值;
2.当存在左孩子结点或右孩子结点时,输出一个“(”符号;
3.递归处理左子树;
4.有右子树时输出一个“,”符号;
5.递归处理右子树;
6.最后输出一个“)”符号;

void DispBTree(BTNode *bt) 
{ 
 if (bt!=NULL)
   {  
   		printf("%c",bt->data);
        if (bt->lchild!=NULL || bt->rchild!=NULL)
         { 
          printf("(");			//有子树时输出'('
	     DispBTree(bt->lchild);	//递归处理左子树
		  if (bt->rchild!=NULL)	//有右子树时输出','
	   	  printf(",");
		 DispBTree(bt->rchild);	//递归处理右子树
		 printf(")");			//输出一个')'
    	 }
   }
}

(7)先序遍历

void PreOrder(BTNode *bt)
{   
	if (bt!=NULL)
 	 {	
 		printf("%c ",bt->data);
		PreOrder(bt->lchild);
		PreOrder(bt->rchild);
    }
}

先序遍历序列的特点:其第一个元素值为二叉树中根结点值。

(8)中序遍历

void InOrder(BTNode *bt)
{   
	if (bt!=NULL)
 	 {	
  	InOrder(bt->lchild);
		printf("%c ",bt->data);
		InOrder(bt->rchild);
    }
}

中序遍历序列的特点:若已知二叉树的根结点值,以该值为界,将中序遍历序列分为两部分,前半部分为左子树的中序遍历序列,后半部分为右子树的中序遍历序列。

(9)后序遍历

void PostOrder(BTNode *bt)
{  
	 if (bt!=NULL)
 	 {	
  	PostOrder(bt->lchild);
		PostOrder(bt->rchild);
		printf("%c ",bt->data);
     }
}

后序遍历序列的特点:最后一个元素值为二叉树中根结点值

(10)层次遍历
层次遍历算法的实现过程:
1.先将根结点进队;
2.在队不空时循环:从队列中出队一个结点p,访问它;若它有左孩子结点,将左孩子结点进队;若它有右孩子结点,将右孩子结点进队。
3.如此操作直到队空为止。

void LevelOrder(BTNode *bt)
{   
	BTNode *p;
  BTNode *qu[MaxSize];	//定义循环队列,存放二叉链结点指针
  int front,rear;		//定义队头和队尾指针
  front=rear=0;		//置队列为空队列
  rear++; 
  qu[rear]=bt;	//根结点指针进入队列
  while (front!=rear)		//队列不为空循环
  {	
  	front=(front+1)%MaxSize;
		p=qu[front];			//出队结点p
		printf("%c ",p->data);	//访问该结点
		if (p->lchild!=NULL)		//有左孩子时将其进队
		{   
			rear=(rear+1)%MaxSize;
	 		qu[rear]=p->lchild;
		}
		if (p->rchild!=NULL)		//有右孩子时将其进队
		{   
			rear=(rear+1)%MaxSize;
	  	qu[rear]=p->rchild;
		}
  }
}

(11)二叉树的拷贝

void CopyBTree(BTNode *bt,BTNode *&nt)
//由二叉树bt复制产生二叉树nt
{   
	if (bt!=NULL)
 	 {	
  		nt=(BTNode *)malloc(sizeof(BTNode));	//复制根结点
			nt->data=bt->data;
			CopyBTree(bt->lchild,nt->lchild);	//递归复制左子树
			CopyBTree(bt->rchild,nt->rchild);	//递归复制左子树
 	 }
  else nt=NULL;		//bt为空树时nt也为空树
}
  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

淮南大甜橘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值