数据结构复习(六)树与森林

一、二叉树

1.了解二叉树

  • 二叉树(Binary Tree)的特点:

每个结点至多只有两棵子树(即二叉树中不存在度大于2的结点)
二叉树的子树有左右之分,其次序不能任意颠倒,分别称为左子树和右子树,左子树和右子树的根称为其双亲的左、右孩子

  • 二叉树的性质

性质1: 在二叉树的第i层上至多有2i-1个结点(i≥1)

性质2:深度为k的二叉树至多有2k-1个结点(k≥1).
(一棵深度为k且正好有2k-1个结点的二叉树称为满二叉树)

性质3: 对任何一棵二叉树,如果其终端结点数为n0,度为2的结点数为n2,则n0=n2+1

性质4: 具有n个结点的完全二叉树的深度为log2(n) +1(向下取整)

性质5: 若对一棵有n个结点的完全二叉树结点进行顺序编号,对任一结点i(1≤i≤n),有:
(1)若i=1,则i是二叉树的根,无双亲;
若i>1,则其双亲编号是i/2(向下取整)
(2)若2i>n,则i为叶子,无左孩子;否则,其左孩子编号是2i
(3)若2i+1>n,则i无右孩子;否则,其右孩子编号是2i+1

2.二叉树的存储结构

  • 顺序存储结构
    适用于完全二叉树

特点:
用一组地址连续的存储单元依次自上而下、自左至右存储完全二叉树的结点。此时可利用性质5很方便地求出结点之间的关系。
对一般二叉树,可将其每个结点与完全二叉树上同一位置上的结点对照,存储在一维数组的相应分量中。可能对存储空间造成极大的浪费

二叉树的顺序存储表示的C语言描述:

#define MAX_TREE_SIZE 100
typedef ElemType SqBiTree[MAX_TREE_SIZE+1];  
//0号单元空闲,1号单元存储根结点
  • 链式存储结构
//二叉链表
  typedef struct BiTNode{
    ElemType data;
    struct BiTNode *lchild,*rchild;
  }BiTNode,*BiTree;
  
n个结点的二叉树的二叉链表中有n+1个空链域

//三叉链表
typedef struct TriTNode{
    ElemType data;
    struct TriTNode *lchild,*rchild,*parent;
}TriTNode,*TriTree;

3.基本操作

遍历

  • 先序遍历(递归)
void PreTraverse(Tree T)
{
	if(T)
	{
	printf("%c ",T->elem);
	PreTraverse(T->left);
	PreTraverse(T->right);
	}
}
  • 中序遍历(递归)
void MidTraverse(Tree T)
{
	if(!T)
	return ;
	MidTraverse(T->left);
	printf("%c ",T->elem);
	MidTraverse(T->right);
}
  • 后序遍历(递归)
void BackTraverse(Tree T)
{
	if(!T)
	return ;
	BackTraverse(T->left);
	BackTraverse(T->right);
	printf("%c ",T->elem);
}
  • 层序遍历
void LevelShow(Tree T)
{
    Queue Q;
    InitQueue(&Q);
    Tree p;
    p=T;
    EnQueue(&Q,p);
    while(!IsQueueEmpty(&Q))
    {
    	DeQueue(&Q,&p);
    	printf("%c ",p->elem);
    	if(p->left)
		    EnQueue(&Q,p->left);
		if(p->right)
		   EnQueue(&Q,p->right);
	}
}
  • 遍历的应用
利用遍历判断两棵二叉树是否相等。
Status Equal(BiTree T1,BiTree T2) {
	if(T1==NULL && T2==NULL) return TRUE; 
	else if(T1==NULL||T2==NULL) return FALSE;	
	else {
	 if(T1->data==T2->data)	
     if(Equal(T1->lchild,T2->lchild))
	 if(Equal(T1->rchild,T2->rchild))  return TRUE; 
         return FALSE;
	}
}//Equal
//利用遍历求二叉树的深度
int depth(BiTree T) {
	if(!T)  return 0;
	depthl=depth(T->lchild);
	depthr=depth(T->rchild);	
    return (depthl>depthr?depthl:depthr)+1;
}//depth
//求叶子节点数目
int LeaveNum(Tree T)
{
	if(T)
	{
		if(T->left==NULL&&T->right==NULL)
		{
			return 1;
		}
		return LeaveNum(T->left)+LeaveNum(T->right);
	}
	return 0;
}
//二叉树左右子树互换
void Exchange(Tree T)
{
    Tree temp;
	if(T)
	{
		temp=T->left;
		T->left=T->right;
		T->right=temp;
		Exchange(T->left);
		Exchange(T->right);
	}
}
删除二叉树中所有以值为x的结点为根的子树并释放相应空间。
void delsubtree(BiTree T){
   if(T){
         delsubtree(T->lchild); 
         delsubtree(T->rchild);
         free(T);
   }//if
}//delsubtree
void DelTree(BiTree &T, TElemType x){
   if(T) {
      if (T->data==x) 
      {
      delsubtree(T); 
      T=NULL;
      }
      else{ 
      DelTree(T->lchild,x);
      DelTree(T->rchild,x);
      }
   }//if
}//DelTree
  • 用二叉树表示表达式的方法
    例:a+b*(c-d)-e/f
    在这里插入图片描述
    描述表达式的二叉树遍历序列
    前缀表达式(波兰式):
    -+ab-cd/ef
    中缀表达式:
    a+b
    c-d-e/f
    后缀表达式(逆波兰式):
    abcd-*+ef/-

二叉树的线索化

我们无法直接得到结点在任意遍历序列中的前驱和后继信息,这种信息只能在遍历的动态过程中才能得到
如何保存这种在遍历过程中得到的信息?
(1)在每个结点上增加两个指针域fwd和bkwd,分别指向结点在按照某种顺序遍历时得到的前驱和后继。 缺点:存储密度低
(2)在有n个结点的二叉链表中必定存在n+1个空链域,可用它们存放结点的前驱和后继。 若结点有左子树,lchild指向左孩子,否则指向其前驱; 若结点有右子树,rchild指向右孩子,否则指向其后继

线索:指向结点前驱或后继的指针
线索二叉树:加上线索的二叉树。
**线索化:**对二叉树以某种次序遍历使其变为线索二叉树的过程。

引入线索二叉树的目的:充分利用二叉链表的空指针域,保存动态遍历过程中得到的结点前驱和后继的信息

  • 树和森林和二叉树之间的转换
    在这里插入图片描述

二、赫夫曼树

1.WPL的计算

路径: 从树中一个结点到另一个结点之间的分支。
路径长度:路径上的分支数目。
树的路径长度:从树根到每个结点的路径长度之和。
结点的带权路径长度:从该结点到树根的路径长度与结点上的权值的乘积
树的带权路径长度WPL:树中所有叶子结点的带权路径长度之和。

设二叉树中共有n个叶子。
wi:树中第i个叶子结点的权值
li :树中第i个叶子结点到根结点的路径长度
在这里插入图片描述

2.huffman树的构造

赫夫曼树:已知n个权值{w1,w2,…wn},构造一棵有n个叶子结点的二叉树,第i个叶子结点的权值是wi,则其中带权路径长度最小的二叉树称为赫夫曼树(Huffman tree)或最优二叉树。

构造过程

步骤1:根据给定的n个权值{w1, w2, …, wn},构造n棵二叉树的集合F = {T1, T2, …, Tn},Ti(1≤i≤n)只有一个带权值wi的根结点,其左、右子树均为空。

步骤2:在F中选取两棵根结点权值最小的二叉树,分别作为左、右子树构造一棵新二叉树。置新二叉树的根结点的权值为其左、右子树上根结点的权值之和。

步骤3: 在F中删去这两棵二叉树,把新的二叉树加入F 。

步骤4: 重复步骤2和步骤3
直到F中仅剩下一棵树为止。
这棵树就是Huffman树。

3.huffman树的编码译码

huffman存储结构

一棵有n个叶子结点的Huffman树共有2n-1个结点
可存储在长度为2n-1的一维数组中。 
typedef struct
{
unsigned int weight; 
unsigned int parent, lchild, rchild; 
}HTNode,*HuffmanTree;

构造huffman树

Status CreateHuffmanTree(HuffmanTree &HT, int *w, int n) 
{  if(n<=1) return ERROR; 
   m=2*n-1; 
   HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode));//0号单元未用
   for(p=HT+1,i=1;i<=n; ++i,++p,++w)    *p={*w,0,0,0}; 
   for(;i<=m; ++i, ++p)     *p= {0,0,0,0}; 
   for(i=n+1;i<=m; ++i)
   { Select(HT,i-1,s1,s2);//选取两个权值最小的结点
     HT[s1].parent=i; HT[s2].parent=i;       
     HT[i].lchild=s1; HT[i].rchild=s2;   
     HT[i].weight=HT[s1].weight+HT[s2].weight; 
   }	
   return OK;
}

编码

Status HuffmanCoding(HuffmanCode &HC,HuffmanTree HT,int n)  
{
    HC=(HuffmanCode)malloc((n+1)*sizeof(char*));   
    cd=(char *)malloc(n*sizeof(char));   
    cd[n-1]='\0';                                     
    for(i=1;i<=n;++i)                             
    {  start=n-1;   
    for(c=i,f=HT[i].parent;f!=0;c=f,f=HT[f].parent)   
       	  if(HT[f].lchild==c)      
       	  cd[--start]='0';                    
         else  
          cd[--start]='1'; 
	HC[i]=(char*)malloc((n-start)*sizeof(char));  		
	strcpy(HC[i],&cd[start]);                 
     }//for
     free(cd);                                   
} //HuffmanCoding 

译码

void Decoding(HuffmanTree HT,HuffmanCode HC,int n)
{
	char ch;
	int i=2*n-1;//从根节点开始往下搜索
    if(ch=='0')
       	i=HT[i].lchild;
    else if(ch=='1')
	    i=HT[i].rchild;
    if(HT[i].lchild==0&&HT[i].rchild==0)//找到了编码对应的第i个节点,打印出第i个节点所对应的字母即可
	     {
	      printf("%c",letter);
		  i=n*2-1;
		 }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

全糖去冰不加料

打赏一块钱💰也是钱

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

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

打赏作者

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

抵扣说明:

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

余额充值