【数据结构】二叉树

二叉树的定义及基本特点

定义

二叉树(Binary Tree)是n(n≥0)个结点所构成的集合,它或为空树(n = 0);或为非空树,对于非空树T:(1)有且仅有一个称之为根的结点 (2)除根结点以外的其余结点分为两个互不相交的子集T1和T2,分别称为T的左子树和右子树,且T1和T2本身又都是二叉树。

基本特点

  • 结点的度小于等于2
  • 有序树(子树有序,不能颠倒)

二叉树的性质

  1. 在二叉树的第i层上至多有 2 i − 1 2^{i-1} 2i1 个结点
  2. 深度为k的二叉树至多有 2 k − 1 2^k -1 2k1 个结点
  3. 对于任何一棵二叉树,若2度的结点数有 n 2 n_2 n2个,则叶子数 n 0 n_0 n0必定为 n 2 + 1 n_2+1 n21 (即 n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1
  4. 完全二叉树:深度为k 的,有n个结点的二叉树,当且仅当其每一个结点都与深度为k 的满二叉树中编号从1至n的结点一一对应
    完全二叉树
  5. 满二叉树:一棵深度为k 且有 2 k − 1 2k -1 2k1个结点的二叉树。
    (特点:每层都“充满”了结点) 满二叉树是完全二叉树的一个特例。
    满二叉树
  6. 具有n个结点的完全二叉树的深度必为 ⌊ l o g 2 n ⌋ + 1 \lfloor log2n \rfloor+1 log2n1
  7. 对完全二叉树,若从上至下、从左至右编号,则编号为i 的结点,其左孩子编号必为 2 i 2i 2i,其右孩子编号必为 2 i + 1 2i+1 2i1;其双亲的编号必为 i 2 \frac {i} {2} 2i

二叉树的存储结构

  1. 顺序存储
    顺序存储特点:
    结点间关系蕴含在其存储位置中
    浪费空间,适于存满二叉树和完全二叉树

  2. 链式存储
    (1)二叉链表

     typedef struct BiNode{
       TElemType   data;
       struct  BiNode   *lchild,*rchild; //左右孩子指针
    }BiNode,*BiTree; 
    

    在n个结点的二叉链表中,有(n+1)个空指针域

    (2)三叉链表

    typedef struct TriTNode{  
    	TelemType data;
    	struct TriTNode *lchild,*parent,*rchild;//左右孩子指针和双亲指针
    }TriTNode,*TriTree;
    

遍历二叉树

遍历规则:

  1. DLR—先序遍历,即先根再左再右
  2. LDR—中序遍历,即先左再根再右
  3. LRD—后序遍历,即先左再右再根

若二叉树中各结点的值均不相同,则:
由二叉树的前序序列和中序序列,或由其后序序列和中序序列均能唯一地确定一棵二叉树,但由前序序列和后序序列却不一定能唯一地确定一棵二叉树。

实例
算法实现:

//递归遍历二叉树
//先序 
void PreOrderTraverse(BiTree T){
  if(T==NULL) return ; //空二叉树
  else{    
     cout<<T->data; //访问根结点
     PreOrderTraverse(T->lchild); //递归遍历左子树
     PreOrderTraverse(T->rchild); //递归遍历右子树
    }
}
//中序 
void InOrderTraverse(BiTree T){
  if(T==NULL) return ; //空二叉树
  else{    
     InOrderTraverse(T->lchild); //递归遍历左子树
  	 cout<<T->data; //访问根结点
     InOrderTraverse(T->rchild); //递归遍历右子树
    }
}
//后序
void PostOrderTraverse(BiTree T){
  if(T==NULL) return ; //空二叉树
  else{    
     PostOrderTraverse(T->lchild); //递归遍历左子树
     PostOrderTraverse(T->rchild); //递归遍历右子树
	 cout<<T->data; //访问根结点
    }
}

//非递归遍历(中序) 
void  InOrderTraverse(BiTree T){
	InitStack(S);
	p=T;//p指向根节点 
	q=new BiTree;//q用来存放栈顶弹出的元素
	while( p || !StackEmpty(S))
	{
		if(p)	//p非空 
		{
			Push(S,p);  	//根指针压入栈 
			p=p->lchild;  	//遍历左子树 
		}
		else
		{
			Pop(S,q);		//出栈 
			cout<<q->data;	//访问根节点 
			p=q->rchild;	//遍历右子树 
		}
	 } 
}

二叉树的建立

//建立二叉树 (先序) 
void CreateBiTree(BiTree &T){
cin>>ch;
if (ch=='#')   T=NULL;  	//递归结束,建空树
else{
    T=new BiTNode;    
	T->data=ch; 	//生成根结点
    CreateBiTree(T->lchild);  //递归创建左子树
    CreateBiTree(T->rchild); //递归创建右子树
  }									
}	

复制二叉树

//复制二叉树
void Copy(BiTree T,BiTree &NewT){
	if(T==NULL)
	{
		NewT=NULL;
		return ;		
	}
	else
	{
		NewT=new BiTree;
		NewT->data=T->data;
		copy(T->lchild,NewT->lchild);
		copy(T->rchild,NewT->rchild);
	}
}

二叉树遍历算法的应用

//计算二叉树叶子节点总数 
int LeadCount(BiTree T){
 	if(T==NULL) 	//如果是空树返回0
		return 0;
	if (T->lchild == NULL && T->rchild == NULL)
		return 1; //如果是叶子结点返回1
	else return LeafCount(T->lchild) + LeafCount(T->rchild);
}

//计算二叉树的深度
int Depth(BiTree T){
 	if(T==NULL) 	//如果是空树返回0
		return 0;
	else
	{
		m=Depth(T->lchild);
		n=Depth(T->rchild);
		if(m>n) return m+1;
		else return n+1;
	}
}

//计算二叉树中结点的个数
int NodeCount(BiTree T) {
	if(T==NULL)
		return 0;
	else
		return NodeCount(T->lchild)+NodeCount(T->rchild)+1;
}

线索化二叉树

利用n+1个空链域增加信息

  1. 若结点有左子树,则lchild指向其左孩子;否则, lchild指向其直接前驱(即线索)
  2. 若结点有右子树,则rchild指向其右孩子;否则, rchild指向其直接后继(即线索)

为了避免混淆,增加两个标志域
线索化二叉树
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

//以结点p为根的子树中序线索化
//pre是全局变量  初始化时其右孩子指针为空,便在数的最左点开始建线索 
void InThreading(BiThrTree p)
{   if(p)
	{
		InThreading(p->lchild);//左子树递归线索化 
		if(!p->lchild)				//没有左孩子 
		{
			p->LTag=1;			//给p加上左线索 
			p->lchild=pre;		//左孩子指针指向前驱pre 
		}
		else
			p->LTag=0;
		if(!pre->rchild)
		{
			pre->RTag=1;
			pre->lchild=p; 		//p的右孩子指针指向其后继p 
		}
		else
			p->RTag=0;
		pre=p;					//保持pre指针p的前驱 
		InThreading(p->rchild); //右子树递归线索化 
	}
	
}

哈夫曼树

相关概念:

  1. 路径:由一结点到另一结点间的分支所构成
  2. 路径长度 :路径上的分支数目
  3. 带权路径长度 :结点到根的路径长度与结点上的乘积
  4. 树的带权路径长度 :树中所有叶子结点的带权路径长度之和
  5. 哈夫曼树:带权路径长度最小的树

哈夫曼树的构造过程

基本思想:使权值大的结点靠近根
操作要点:对权值的合并、删除与替换,总是合并当前值最小的两个

一棵有n个叶子结点的Huffman树有 2 n − 1 2n-1 2n1个结点,故采用顺序存储结构

结点类型定义

typedef  struct
{  int weght;
   int parent,lch,rch;
}*HuffmanTree;

初始化并创建哈夫曼树

void CreatHuffmanTree (HuffmanTree HT,int n){
	if(n<=1)return;
	m=2*n-1;
	HT=new HTNode[m+1];//0号单元未用,HT[m]表示根结点   
	for(i=1;i<=m;++i){
	  HT[i].lch=0;
	  HT[i].rch=0;
	  HT[i].parent=0;}
	for(i=1;i<=n;++i)
		cin>>HT[i].weight; 
	for( i=n+1;i<=m;++i)       //构造  Huffman树
	{ Select(HT,i-1, s1, s2); 
	      //在HT[k](1≤k≤i-1)中选择两个其双亲域为0,
	      // 且权值最小的结点,
	      // 并返回它们在HT中的序号s1和s2
	   HT[s1].parent=i;   HT[s2] .parent=i;  
	     //表示从F中删除s1,s2
	   HT[i].lch=s1;    HT[i].rch=s2 ; 
	     //s1,s2分别作为i的左右孩子
	   HT[i].weight=HT[s1].weight + HT[s2] .weight;
	     //i 的权值为左右孩子权值之和
	  }
  }

void Select(HuffmanTree& HT, int j, int& d1, int& d2)//获取两个较小值的位置 
{
	int min[2] = { 10000,10000 };
	for (int i = 1; i < j; i++)
	{
		if (HT[i].parent == 0)
		{
			if (HT[i].weight < min[0])
			{
				min[1] = min[0];
				min[0] = HT[i].weight;
				d2 = d1;
				d1 = i;
			}
			else if (HT[i].weight < min[1])
			{
				min[1] = HT[i].weight;
				d2 = i;
			}
		}
		else
			continue;
	}
}

从叶子到根逆向求每个字符的哈夫曼编码,存储在编码表HC中

void CreatHuffmanCode(HuffmanTree HT, HuffmanCode &HC, int n){
//从叶子到根逆向求每个字符的赫夫曼编码,存储在编码表HC中
HC=new char *[n+1];         		//分配n个字符编码的头指针矢量
cd=new char [n];			//分配临时存放编码的动态数组空间
cd[n-1]=’\0; 	                                //编码结束符
for(i=1; i<=n; ++i){	                //逐个字符求赫夫曼编码
    start=n-1; c=i; f=HT[i].parent;                 			
    while(f!=0){	                                                 //从叶子结点开始向上回溯,直到根结点
         --start;                          			//回溯一次start向前指一个位置
         if (HT[f].lchild= =c)  cd[start]=0;	//结点c是f的左孩子,则生成代码0
         else cd[start]=1;               		//结点c是f的右孩子,则生成代码1
         c=f; f=HT[f].parent;             		//继续向上回溯
    }                                  		                //求出第i个字符的编码      
    HC[i]= new char [n-start];         	                // 为第i 个字符编码分配空间
    strcpy(HC[i], &cd[start])//将求得的编码从临时空间cd复制到HC的当前行中
  }
  delete cd;                            	    //释放临时空间
} 
  • 8
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值