树和二叉树

层次结构的数据在实现世界中大量存在.例如,一个国家有若干个省,一个省有若干个市等
线性数据结构一般不适合用来表示层次数据.为了组织层次数据,需要对树型数据的结构进行研究.

(1) 树的定义

描述树状结构采用的是倒置树,倒置树的顶端是根,根有几个分枝,称为子树,
每棵子树再分成几个小分枝,小分枝再分为更小的分枝,每个分枝也都是树,一个结点也是树.

树(tree)是包括n各结点的有限非空集合T,
其中,一个特定的结点r称为根(root),区域结点T-{r}划分成m(m>=0)个互不相交的子集T1,T2,T3..Tm,
其中,每个子集都是树,称为树根r的子树.
根据树的定义,一个结点的树是仅有根结点的树,这时m=0,这棵树没有子树.

(2) 基本术语

树中元素称为结点(node)
如果某结点到另一结点之间存在着连线,则称此线为一条边(edge)
如果从某个结点沿着树中的边可到达另一个结点,则称这两个结点间存在一条路径(path)
如果一个结点有子树,则称该结点为子树的双亲(parent)
子树的根称该结点的孩子(child)
具有相同双亲的结点称为兄弟(sibling)
结点拥有的子树数称为结点的度(degree)
度为0的结点称为叶子(leaf)
规定根结点的层次为1,树中结点的最大层次称为树的高度(hight)
如果树中结点的各子树是从左至右有次序的,则称该树为有序树,否则称为无序树.

(3)二叉树

二叉树(binary tree)是由有限个结点组成的集合,
它或者为空集合,或者仅含一个根结点,或者由一个根结点和两棵互不相交的左,右子树组成.
树定义与二叉树定义稍有区别.
首先树不能为空树,却可以有空二叉树.
一般树的子树不分次序的,二叉树的子树却分左右子树.
一般树中结点的度可以大于2,二叉树中结点的度均不超过2.

(4)二叉树的性质

若二叉树的高为h,树的结点总数为2^h-1,称此二叉树为满二叉树.
对于高度为h的二叉树,
如果第1-(h-1)层构成满二叉树,而第h层的叶子结点严格按照从左到右依次排列,称此二叉树为完全二叉树. 
性质1: 高度为h的二叉树的结点总数 n <= 2^h-1.
性质2: 对于含n(n>=1)个结点的完全二叉树,其高度h=[log2(n+1)].([]表示取整)
性质3: 对于一棵非空二叉树,如果度为0的结点数为n0,度为2的结点数为n2,则n0=n2+1;
性质4: 对于含n个结点的完全二叉树,按从上到下,从左到右的顺序,从0到n-1编号.对于树中编号为i(0<=i<=n-1)的结点,有:
(1) 如果i=0;则该结点为二叉树的根结点.
(2) 如果2i+1<n,则其左孩子为2i+1,否则不存在左孩子;
	如果2i+2<n,则其左孩子为2i+2,否则不存在右孩子.
(3) 结点i的双亲为[(n-1)/2]

(5)二叉树的ADT

ADT Bintree
数据:
0个或有限个结点组成的集合.
运算:
	Create()        创建一棵空二叉树或二叉树 
	IsEmpty()		若二叉树为空,则返回 1,否则返回 0;	
	PreOrder()		先序遍历 
	InOrder()		中序遍历 
	PostOrder()		后序遍历	
	Display()		输出二叉树 

(6)二叉树的数组表示

用一维数组来存储二叉树,首先将二叉树想象成一棵完全二叉树,
对于没有左孩子或右孩子的结点,用特殊字符代替,
再依次从上至下,从左至右的次序,依次将结点值存放到一维数组之中.
#include<iostream>
#include<cmath>
using namespace std;

#define	MaxSize 64  //结点数不超过63,即树高不超过6
//创建二叉树
void Create(char Node[],int &n){
	cout<<"按完全二叉树输入结点字符,空字符用*代替"<<endl;
	cin>>Node;
	//计算所含的字符数
	n=0;
	while(Node[n]!='\0')
		n++; 
} 
//输出二叉树 
void Display(char Node[],int n){
	int hight,layer,num,i,j,k; //hight 树高 layer树层 num结点编号
	hight=ceil(log(n+1)/log(2));
	num=0;
	for(layer=1;layer<=hight;layer++){ //输出 1层 到hight层 
		//每层前面空格的输出控制 
		for(i=0;i<pow(2,hight-layer);i++)
			cout<<" "; 
		//每层结点的输出控制
		for(j=0;j<pow(2,layer-1);j++,num++){
			if(Node[num]!='*'&&Node[num]!='\0')
				cout<<Node[num];
			else if(Node[num]=='*')	
				cout<<" ";
			else 
				break;
			//每层结点间空格的输出控制	
			for(k=0;k<pow(2,hight-layer+1)-1;k++)
				cout<<" ";		
		}
		cout<<endl;	
	}  
}		
int main(){
	int n;
	char Node[MaxSize];
	Create(Node,n);
	cout<<"二叉树为:"<<endl;
	Display(Node,n); 
}		

(7) 二叉树的链表表示

链式二叉树结点的逻辑结构:[*lChild | data | *rChild]
#include<iostream>
using namespace std;

//二叉树结点定义
struct Node{
	char data;
	Node *lChild,*rChild;
};
//二叉树的定义 
struct BinTree{
	Node *root;
};

//创建二叉树 
void Create(BinTree &T,char x,Node *left,Node *right){
	Node *NewNode;
	NewNode = new Node;
	NewNode->data=x;
	NewNode->lChild=left;
	NewNode->rChild=right;
	T.root=NewNode; 
} 
//前序遍历
void PreOrder(Node *p){
	if(p!=NULL){
		cout<<p->data;
		PreOrder(p->lChild);
		PreOrder(p->rChild);
	}
} 
//中序遍历 
void InOrder(Node *p){
	if(p!=NULL){
		InOrder(p->lChild);
		cout<<p->data;
		InOrder(p->rChild);
	}
}
//后序遍历
void PostOrder(Node *p){
	if(p!=NULL){
		PostOrder(p->lChild);
		PostOrder(p->rChild);
		cout<<p->data;		
	}
}
int main(){
	BinTree a,b,c,e,k;
	Create(k,'k',NULL,NULL);
	Create(e,'e',NULL,k.root);
	Create(b,'b',NULL,e.root);
	Create(c,'c',NULL,NULL);
	Create(a,'a',b.root,c.root);
	
	cout<<"前序遍历:";PreOrder(a.root);cout<<endl;
	cout<<"中序遍历:";InOrder(a.root);cout<<endl;
	cout<<"后序遍历:";PostOrder(a.root);cout<<endl;		 
}

定理: 任意n(n>=0)个不同结点的二叉树,都可由其前序遍历序列和中序遍历序列唯一确定.

(8) 常用二叉树之排序二叉树

排序二叉树定义:
设二叉树的所有结点值互异,满足一下条件:
(1)若左子树不空,则左子树上所有结点的值均小于根结点的值
(2)若右子树不空,则左子树上所有结点的值均大于根结点的值
(3)左右子树也分别为排序二叉树 
#include<iostream>
using namespace std;

//二叉树结点定义
struct Node{
	int data;
	Node *lChild,*rChild;
};
//二叉树的定义 
struct BinTree{
	Node *root;
};
//创建空二叉树
void Create0(BinTree &T){
	T.root=NULL;
} 
//判空
int IsEmpty(BinTree &T){
	if(T.root==NULL) return 1;
	else return 0;
} 
//创建排序二叉树
void Create(BinTree &T,int x){
	Node *NewNode,*p;
	NewNode = new Node;
	NewNode->data=x;
	NewNode->lChild=NULL;
	NewNode->rChild=NULL;
	int flag=0;  //标识:是否入树  0表示为入树
	if(IsEmpty(T)) T.root=NewNode;
	else{
		p=T.root;
		while(!flag){
			if(x<p->data){ //进入左子树 
				if(p->lChild == NULL){
					p->lChild=NewNode;
					flag=1;
				}else{
					p=p->lChild;
				} 
			}else{         //进入右子树 
				if(p->rChild == NULL){
					p->rChild=NewNode;
					flag=1;
				}else{
					p=p->rChild;
				}
			}	
		}	
	} 	
} 
// 创建二叉树
void Create1(BinTree &T,char x,Node *left,Node *right){
	Node *NewNode;
	NewNode = new Node;
	NewNode->data=x;
	NewNode->lChild=left;
	NewNode->rChild=right;
	T.root=NewNode; 
} 
//前序遍历
void PreOrder(Node *p){
	if(p!=NULL){
		cout<<p->data<<" ";
		PreOrder(p->lChild);
		PreOrder(p->rChild);
	}
} 
//中序遍历 
void InOrder(Node *p){
	if(p!=NULL){
		InOrder(p->lChild);
		cout<<p->data<<" ";
		InOrder(p->rChild);
	}
}
//后序遍历
void PostOrder(Node *p){
	if(p!=NULL){
		PostOrder(p->lChild);
		PostOrder(p->rChild);
		cout<<p->data<<" ";		
	}
}
int main(){
	BinTree T;
	Create0(T);
	int i=1,x;
	cout<<"请输入二叉树结点的值,输入-1结束"<<endl;
	while(1){
		cout<<"请输入第"<<i<<"个结点的值:";
		cin>>x;
		if(x == -1) break;
		else  Create(T,x);
		i++; 
	} 
	cout<<"前序遍历:";PreOrder(T.root);cout<<endl;
	cout<<"中序遍历:";InOrder(T.root);cout<<endl;
	cout<<"后序遍历:";PostOrder(T.root);cout<<endl;
} 

排序二叉树的优点:建树方便,中序遍历为升序序列. 

(9) 常用二叉树之堆

设k0,k1,k2,...kn-1为互异的结点值,且按完全二叉树方式存放在一维数组k(0...n-1)之中
如果 k[i]>=k[2i+1]且k[i]>=k[2i=2](或k[i]<=k[2i+1]且k[i]<=k[2i=2])  i=0,1...(n-1)/2
则称该集合为最大堆(max heap)(或最小堆(min heap))
最大堆的意义是:双亲结点值大于等于其左右孩子结点值.
最小堆的意义是:双亲结点值小于等于其左右孩子结点值.	 
用下滑法,可以将完全二叉树调整为最大堆.
#include<iostream>
#include<iomanip>
using namespace std;
//输出数组
void Display(int *k,int n){
	for(int i=0;i<n;i++)
		cout<<setw(3)<<k[i];
	cout<<endl;
} 
//下滑法调最大堆
void SiftDown1(int *k,int n){
	int i,j,temp,parent;
	int m=0;
	for(parent=(n-2)/2;parent>=0;parent--){
		i=parent;
		temp=k[i];
		j=2*i+1;
		while(j<n){
			if(j+1<n)
				j=k[j]<k[j+1]? j+1:j;
			if(temp>k[j])
				break;
			else{
				k[i]=k[j];
				i=j;
				j=2*i+1;
			}		
		}
		k[i]=temp;
		cout<<++m<<"趟调堆:";Display(k,n); 
	} 
} 
//下滑法调最小堆
void SiftDown2(int *k,int n){
	int i,j,temp,parent;
	int m=0;
	for(parent=(n-2)/2;parent>=0;parent--){
		i=parent;
		temp=k[i];
		j=2*i+1;
		while(j<n){
			if(j+1<n)
				j=k[j]>k[j+1]? j+1:j;
			if(temp<k[j])
				break;
			else{
				k[i]=k[j];
				i=j;
				j=2*i+1;
			}		
		}
		k[i]=temp;
		cout<<++m<<"趟调堆:";Display(k,n); 
	} 
} 
int main(){
	int k[9]={32,17,16,24,35,87,65,4,12};
	cout<<"初始数据:";Display(k,9);
	cout<<"最大堆"<<endl; 
	SiftDown1(k,9); 
	cout<<"最小堆"<<endl; 
	SiftDown2(k,9); 	
} 
最大堆具有以下性质:	
(1)堆顶结点的值最大
(2)任一结点的值均大于其左右孩子的值
(3)任一结点的值按完全二叉树存储在一维数组中

(10) 常用二叉树之哈夫曼树

路径长度(path length):路径上的分支数目
树的路径长度(tree path length):根结点到每个叶子结点的路径长度之和树的带权路径长度(weighted path length):
WPL =sum(w(i)l(i)) i=1...n	
w(i)是第i个叶子结点的权值,l(i)为从根结点到第i个叶子结点的路径长度,
n为叶子结点的总数. 
最优二叉树:WPL最小的二叉树,最优二叉树也称为哈夫曼树(Huffman tree)
哈夫曼树的性质:
	(1) 哈夫曼树不存在度为 1 的结点.
	(2) 哈夫曼树的带权路径长度等于所有度为 2 的结点值之和. 
哈夫曼编码:
以不同字符出现的频树为权值,创建一棵哈夫曼树,
将根结点到叶子结点的路径编号,左分支路取 0,右分支路径取 1,
于是,任一叶子结点到根结点的路径序列就是它的哈夫曼二进制编码. 
#include<iostream>
#include<iomanip>
using namespace std;
#define N 8  //叶子结点个数

//哈夫曼结点定义
struct hNode{
	int suffix;       //结点下标
	int weight;       //权值
	int parent,lChild,rChild;//双亲,左右孩子下标 
}; 
//哈夫曼编码结点定义
struct cNode{
	int suffix;       //结点下标
	int weight;       //权值
	int length;       //编码长度
	char *code;       //编码字符串 
}; 
//哈夫曼结点数组初始化
void Initial(hNode h[],int w[]){
	for(int i=0;i<N;i++){ //c初始化哈夫曼数组前半部分
		h[i].suffix=i;	  //叶子结点的标号从0到N-1 
		h[i].weight=w[i];
		h[i].parent=-1;
		h[i].lChild=-1;
		h[i].rChild=-1; 
	}
	for(int i=N;i<2*N-1;i++){ //c初始化哈夫曼数组后半部分
		h[i].suffix=i;	  //非叶子结点的标号从N到2*N-1 
		h[i].weight=0;
		h[i].parent=-1;
		h[i].lChild=-1;
		h[i].rChild=-1; 
	}
} 

//对哈夫曼结点h[m],....h[n]进行排序 
//action=0 或 1 分别表示依权值或下标做升序排序
void Sort(hNode h[],int m,int n,int action){
	int i,j,pos,min,flag;
	hNode temp;
	if(action == 0){
		for(i=m;i<n-1;i++){
			min=h[i].weight;
			pos=i;
			flag=0;
			for(j=i+1;j<=n;j++)
				if(h[j].weight<min){
					min=h[j].weight;
					pos=j;
					flag=1;
				}
			if(flag==1){
				temp=h[i];
				h[i]=h[pos];
				h[pos]=temp;
			}	
		}
	}else{
		for(i=m;i<n-1;i++){
			min=h[i].suffix;
			pos=i;
			flag=0;
			for(j=i+1;j<=n;j++)
				if(h[j].suffix<min){
					min=h[j].suffix;
					pos=j;
					flag=1;
				}
			if(flag==1){
				temp=h[i];
				h[i]=h[pos];
				h[pos]=temp;
			}	
		}
	}
} 
//输出哈夫曼结点数组
void OutputNode(hNode h[]){
	cout<<setw(5)<<"下标"<<setw(5)<<"权值"<<setw(5)<<"双亲"<<setw(7)<<"左孩子"<<setw(7)<<"右孩子"<<endl;
	for(int i=0;i<N*2-1;i++)
	cout<<setw(5)<<h[i].suffix<<setw(5)<<h[i].weight<<setw(5)<<h[i].parent<<setw(7)<<h[i].lChild<<setw(7)<<h[i].rChild<<endl; 
} 
//输出哈夫曼编码数组
void OutputCode(cNode c[]){
	cout<<setw(5)<<"下标"<<setw(5)<<"权值"<<setw(5)<<"码长"<<setw(7)<<"编码"<<endl;
	for(int i=0;i<N;i++)
	cout<<setw(5)<<c[i].suffix<<setw(5)<<c[i].weight<<setw(5)<<c[i].length<<setw(7)<<c[i].code<<endl; 
} 
//将含N个叶子结点的哈夫曼初始数组,合并成哈夫曼树数组
void Merge(hNode h[]){
	int i,j;
	for(i=0;i<N-1;i++){ //对于N个叶子结点,做N-1次合并,构建哈夫曼树 
		j=i*2;          //确定排序的起始位置 
		Sort(h,j,N+i-1,0);
		//将N+i个结点的权值等于两个较小权值结点之和
		h[N+i].weight=h[j].weight+h[j+1].weight;
		h[N+i].lChild=h[j].suffix;			//左孩子为h[j]的下标 
		h[N+i].rChild=h[j+1].suffix;		//右孩子为h[j+1]的下标 
		h[j].parent=h[j+1].parent=N+i;      //h[j],h[j+1]的双亲下标为N+i 
	}
} 
//对哈夫曼树进行编码
void Code(hNode h[],cNode c[]){
	int i,j,k,len;
	char temp[N];     					//定义临时存放叶子结点编码的字符数组 
	for(i=0;i<N;i++){ 					//对h[]中的前N个叶子结点依次编码 
		len=0;        					//编码长度初始为0
		//用回溯法,对叶子结点i进行编码,并将编码暂存temp中
		for(j=i,k=h[i].parent;k!=-1;j=k,k=h[k].parent){
			if(h[k].lChild==j)        	//如果叶子结点i是它双亲的左孩子,则编码取 '0' 否则取 '1' 
				temp[len]='0';      
			else
				temp[len]='1';
			len++;		
		} 
		c[i].suffix=h[i].suffix;
		c[i].weight=h[i].weight;
		c[i].length=len;
		//将存放在temp中的叶子结点i的编码,反转导入c[i].code 
		c[i].code=new char[len+1];
		c[i].code[len]='\0';
		len--;
		j=0;
		while(len>=0)
			c[i].code[j++]=temp[len--];
	}
} 
int main(){
	int w[N]={3,4,5,8,11,20,21,28};
	hNode hArray[2*N-1];
	Initial(hArray,w);
	Merge(hArray);			//合并成哈夫曼树 
	cout<<"哈夫曼树为:"<<endl;
	OutputNode(hArray);
	cNode cArray[N];  
	//依下标对哈夫曼树做升序排序,使前N个结点是叶子结点. 
	Sort(hArray,0,2*N-2,1);  
	Code(hArray,cArray);     //编码 
	cout<<"哈夫曼编码为:"<<endl;
	OutputCode(cArray);	 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值