数据结构-树和二叉树

树的概念

定义:T={D,R}。D是包含n个节点的有限集合(n>=0)。当n=0时为空树,否则:
1、没有前趋节点,根节点。
2、除根节点外,每个节点有且仅有一个前趋节点。
3、D中每个节点可以有零个或多个后继节点。

逻辑表示

1、树形表示法
2、文氏图表示法
3、凹入表示法
4、括号表示法

基本术语

1、节点的度与树的度:树中一个节点的子树的个数称为该节点的度。树中各节点的度的最大值称为树的度。
2、分支节点与叶节点:度不为零的节点称为非终端节点,又叫分支节点。度为零的节点称为终端节点或叶节点。
3、路径长度等于路径所通过的节点数目减1
4、孩子节点、双亲节点、兄弟节点、子孙节点、祖先节点
5、节点的层次:树中每个节点都处在一个层次上,根节点为第1层。
树的高度/深度:树中节点的最大层次。
6、有序树:若树中各节点的子树是按照一定次序从左到右安排的,且相对次序不能随意变化。否则无序树。
7、森林:n个互不相交的树的集合。 //独木也成林

树的性质

1、树中的节点数等于所有节点的度数加一
所有节点的度之和=分支数
实际分支数/度之和=节点数-1
2、度为m的树中第i层上至多有m^(i-1)个节点
3、高度为h的m次树至多有(m^h-1)/(m-1)个节点
4、具有n个节点的m次树的最小高度为[logm(n(m-1)+1)]

树的基本运算

二叉树

定义

二叉树是有限的节点集合。这个集合或者是空,或者由一个根节点和两棵互不相交的成为左、右子树的二叉树组成。

逻辑表示法

树形表示法、文氏图表示法、凹入表示法、括号表示法

特殊二叉树

满二叉树:所有分支节点都有双分节点,并且叶节点都集中在二叉树的最下一层。高度为h的二叉树恰好有2^h-1个节点。
完全二叉树:最多只有下面两层的节点的度数小于2,并且最下面一层的叶节点都依次排列在该层最左边的位置。

性质

1、非空二叉树上叶节点数于双分支节点数加1 n0=n2+1
n0+n2+n1-1=n1+2n2

2、非空二叉树上第i层上至多有2^(i-1)个节点

3、高度为h的二叉树至多有2^h-1个节点

4、除树根节点外,若一个节点编号为u,则它的双亲节点的编号为i/2]
若编号为i的节点有左孩子节点,2i;右孩子,2i+1

二叉树的存储结构

二叉树的顺序存储结构

完全二叉树适合顺序存储。一般二叉树8行,特别对于那些单分支节点较多的,因为可能只有少数存储单元被利用。
容易找到一个节点的双亲和孩子。

二叉树的链式存储结构

节点类型定义:

typedef struct node
{	ElemType data;
	struct node *lchild,*rchild;
}BTNode;

特点:节省存储空间。找一个节点孩子容易,找双亲不便。

二叉链中,空指针域个数:
n个节点-》2n个指针域
分支数n-1-》非空指针域n-1
空指针域=2n-(n-1)=n+1

二叉树基本运算及实现

1、创建二叉树
括号表示法:

void CreateBTNode(BTNode *&b,char *str)
{	BTNode *St[MaxSize],*p;
	int top=-1,k,j=0;
	char ch;
	b=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(b==NULL)  //p为二叉树的根节点
	        b=p;
	    else
	    {	switch(k)
	    	{
	    		case 1:St[top]->lchild=p;break;
	    		case 2:St[top]->rchild=p;break;
	    	}
	    }
	    }
	j++;ch=str[j];  //继续扫描str
}}

2、销毁二叉链

void DestroyBT(BTNode *&b)
{	if(b==NULL) return;
	else
	{	DestroyBT(b->lchild);
		DestroyBT(b->rchild);
		free(b);
	}
}

3、查找节点

BTNode *FindNode(BTNode *b,ElemType x)
{	BTNode *p;
	if(b==NULL) return NULL;
	else if(b->data==x)return b;
	else
	{	p=FindNode(b->lchild,x);
		if(p!=NULL)return p;
		else return FindNode(b->rchild,x);
	}
}

4、找孩子节点

BTNode *LchildNode(BTNode *p)
{	return p->lchild;}

BTNode *RchildNode(BTNode *p)
{	return p->rchild;}

5、输出二叉树

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

二叉树的遍历

1、 递归算法
先序遍历:

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

中序遍历:

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

后序遍历:

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

2、层次遍历
思路:使用一个队列。将根节点进队,队不空时循环,从队列中出列一个节点*p,访问它:
若它有左孩子节点,将左孩子节点进队。
若它有右孩子节点,将右孩子节点进队。

void LevelOrder(BTNode *b)
{	BTNode *p;
	BTNode *qu[MaxSize]; //定义环形队列,存放节点指针
	int front,rear;
	front=rear=0;
	rear++;
	qu[rear]=b;
	while(front!=rear)
	{	front=(front+1)%MaxSize;
		p=qu[front];
		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;
		}
	}
}

二叉树的构造

同一棵二叉树具有唯一的先序、中序、后序序列。
但不同二叉树可能有相同先序、中序、后序序列。
先序、中序和后序都给定可以唯一构造出二叉树。

同时给定一棵二叉树的先序序列和中序序列、中序序列和后序序列就能唯一确定这棵二叉树。 先序序列和后序序列不行

由先序序列和中序序列构造二叉树:

BTNode *CreateBT1(char *pre.char *in,int n)
{	BTNode *s,char *p,int k;
	if(n<=0) return NULL;
	s=(BTNode *)malloc(sizeof(BTNode));
	s->data=*pre;
	for(p=in;p<in+n;p++)
		if(*p==*pre) break;
	k=p-in;
	s->lchild=CreateBT1(pre+1,in,k);//构造左子树
	s->rchild=CreateBT1(pre+k+1,p+1,n-k-1);//构造右子树
	return s;
	}
	```

由中序序列和后序序列构造二叉树:

```c
BTNode *CreateBT2(char *post,char *in,int n)
{	BTNode *b;char r,*p;int k;
	if(n<=0) return NULL;
	r=*(post+n-1);//根节点值
	b=(BTNode *)malloc(sizeof(BTNode));//创建二叉树节点*b
	b->data=r;
	for(p=in;p<in+n;p++) if(*p==*r) break;
	k=p-in;
	b->lchild=CreateBT2(post,in,k);//递归构造左子树
	b->rchild=CreateBT2(post+k,p+1,n-k-1);//递归构造右子树
	return b;
}

线索二叉树

采用某种方法遍历二叉树的结果是一个节点的线性序列。
修改空链域改为存放指向节点的前驱和后继节点的地址。
这样指向该线性序列中前驱和后继的指针称作线索。
创建线索的过程称为线索化。线索化的二叉树称为线索二叉树。
意义:提高遍历过程效率

在节点存储结构上增加两个标志位区分:
左标志ltag:
0 表示lchild指向左孩子节点
1 表示lchild指向前趋节点,即左线索

右标志rtag:
0 表示rchild指向右孩子节点
1 表示rchild指向后继节点,即右线索

为方便计算,再增加一个头节点。

线索化二叉树中节点类型定义如下:

typedef struct node
{	ElemType data;//节点数据域
	int ltag,rtag;//增加的线索标记
	struct node *lchild;
	struct node *rchild;
}TBTNode;

中序线索化二叉树算法如下:

TBTNode *pre;//全局变量
TBTNode *CreateThread(TBTNode *b)
{	TBTNode *root;
	root=(TBTNode *)malloc(sizeof(TBTNode));//创建头节点
	root->ltag=0;root->rtag=1;root->rchild=b;
	if(b==NULL) root->lchild=root;//空二叉树
	else
	{	root->lchild=b;
		pre=root;//pre是*p的前趋节点,供加线索用
		Thread(b);//中序遍历线索化二叉树
		pre->rchild=root;//最后处理,加入指向头节点的线索
		pre->rtag=1;
		root->rchild=pre;//头节点右线索化
	}
	return root;
}
void Thread(TBTNode *&p)
{	if(p!=NULL)
	{
		Thread(p->lchild);
		if(p->lchild==NULL)
		{	p->lchild=pre;p->ltag=1;}//建立当前节点的前趋线索
		else p->ltag=0;
		if(pre->rchild==NULL) //后继线索化
		{pre->rchild=p;pre->rtag=1;}//建立前趋节点的后及线索
		else pre->rtag=0;
		pre=p;
		Thread(p->rchild);//递归调用右子树线索化
	}
}

中序遍历线索化二叉树:

void ThInOrder(TBTNode *tb)
{	TBTNode *p=tb->lchild;
	while(p!=tb)
	{
		while(p->ltag==0)p=p->lchild;
		printf("%c",p->data);
		while(p->rtag==1&&p->rchild!=tb)
		{	p=p->rchild;
			printf("%c",p->data);
		}
		p=p->rchild;
	}
}

哈夫曼树

哈夫曼树

设二叉树具有n个带权值的叶节点,那么从根节点到各个叶节点的路径长度与相应节点权值乘积之和,叫做二叉树的带权路径长度。
具有最小带权路径长度的叫哈夫曼树。
原则:
1、权值越大的叶节点越靠近根节点
2、权值越小的叶节点越远离根节点

构造过程:
1、给定的n个权值构造n棵只有一个叶节点的二叉树,从而得到一个二叉树的集合。
在F中选取根节点的权值最小和次小的两棵二叉树作为左、右子树构造一棵新的二叉树,这棵新的二叉树根节点的权值为其左、右子树根节点权值之和
在集合F中删除作为左、右子树的两棵二叉树,并将新建立的二叉树加入到集合F中
特点:
n1=0
n=n0+n2
=2n0-1

哈夫曼编码

规定哈夫曼树中左分支为0,右分支为1。权值越大的哈夫曼编码越短,权值越小哈夫曼编码越长。

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值