目录
一、树
5.1 树的定义
树(Tree)是n个结点的有限集,它或为空树(n=0);或为非空树,对非空树T;
1)有且仅有一个称之为根的结点;
2)除根节点以外的其余结点可分为m(m>0)个互不相交的有限集,其中每个集合本身又是一棵树,并且称为根的子树(SubTree)
由此可见,树的定义其实是一种递归的定义,子树同样也可以是一棵树,值得注意的是,一个单个的元素也被视为只有一个结点的树。
5.2 树的基本术语
1)结点:树中的一个独立单元,包含对一个数据元素及若干指向其子树的分支。
2)结点的度:结点拥有的子树数称为子树的度(类似于无向图中度的定义)。
3)树的度:根结点的度(树内各结点度的最大值)。
4)叶子:度为0的结点或终端结点。
5)非终端结点:度不为0的结点称为非终端结点或分支结点。
6)双亲结点和孩子:结点的子树的根称为该结点的孩子;相应的,该结点称为孩子的双亲。
7)兄弟:同一个双亲的孩子之间互称为兄弟。
8)祖先:从根到该结点所经分支上的所有结点(这里到该结点指的是最短路)。
9)子孙:以某结点为根的子树中任一结点都称为该结点的子孙。
10)层次:结点的层次从根开始定义起,根为第一层,根的孩子为第二层。树中任一结点的层次等于其双亲结点的层次加1.
11)堂兄弟:双亲在同一层次的结点互为堂兄弟。
12)树的深度:树中结点的最大层次称为树的深度或高度。
13)有序树和无序树:如果将树中的结点的个子树看成从左至右是有次序的(不能互换),则称该树为有序树,否则为无序树。在有序树中最左边的子树的根称为第一个孩子,最右边的称为最后一个孩子。
14)森林:是m棵互不相交的树的集合。对树中每个结点而言,其子树的集合即为森林。由此,我们也可以用森林和树相互递归定义来描述树。
换言之,任何一棵树都可以看作一个二元组 ,其中root是数据元素,称作树的根结点;F是m棵树的森林,,其中称作根root的第i棵子树。
5.3 树的存储结构
树的存储结构很多,这里主要介绍三种:
5.3.1 双亲表示法
这种存储方式指针域只需要一个指向双亲结点的指针,比较节省空间但是不利于从上至下的遍历。
5.3.2 孩子表示法
指针域存储的是所有孩子结点的地址,比较费空间,只能从上至下遍历。
5.3.3 孩子兄弟表示法
又称二叉树表示法,或二叉链表示法,以二叉链表做树的存储结构,这种存储方式和后面的二叉树的二叉链表表示法完全一样,本质上两个指针记录的是两个子结点的地址,进行访问。
二、二叉树
5.1 二叉树的定义
二叉树是一种树,主要需要注意的是二叉树每个结点至多有两棵子树且二叉树的子树有左右之分,次序不能任意颠倒
5.2 二叉树的性质
1)二叉树第i层至多有个结点
2)深度为k的二叉树最大结点数为
3)对任意一棵二叉树T,如果其终端结点数为,度数为2的结点数为,则
满二叉树:深度为k且有个结点的二叉树
完全二叉树:深度为k的,有n个结点的二叉树,当且仅当每一个节点都与深度为k的满二叉树中编号从1到n的结点一一对应时,称为完全二叉树
完全二叉树>满二叉树
完全二叉树的性质:n个结点的完全二叉树深度为
5.3 二叉树的存储
由于二叉树的逻辑结构,二叉树的存储既可以顺序存储也可以链式存储。但由于对于一般的二叉树,为了存储完所有结点往往会造成空间的浪费,所以对于一般的二叉树更适用链式存储。
而对于链式存储的二叉树,我们的指针域也会有所区别,主要有两种方式:
5.3.1 二叉链表
顾名思义,即指针域只有两个指针:左子结点和右子结点,这种方式由于无双亲结点的地址,因此只能从上至下遍历,无法逆序。
5.3.2 三叉链表
比二叉链表多了一个双亲结点的指针,因此可以任意遍历,但是占用空间更多
5.4 遍历二叉树和线索二叉树
5.4.1二叉树的遍历
根据排列组合,我们的二叉树遍历主要有三种方法(先序遍历,中序遍历和后续遍历),有时还会用到第四种遍历方式:层序遍历,层序遍历就是按照树的层次从左往右遍历,而另外三种其实描述的先后指的就是双亲结点是优先访问,中间访问还是最后访问。三种遍历方式其实差别不大。
void find(int no) {
if(no>n) return;
find(no*2);
find(no*2+1);
/*操作*/
}//后序遍历
void find(int no) {
if(no>n) return;
find(no*2);
/*操作*/
find(no*2+1);
}//中序遍历
void find(int no) {
if(no>n) return;
/*操作*/
find(no*2);
find(no*2+1);
}//前序遍历
值得注意的是,只有确定了中序遍历次序和任意一种遍历次序,一棵二叉树才算确定下来了。
5.4.2 线索二叉树
遍历二叉树的其实就是以一定规则将二叉树中的结点排列成一个线性序列,得到二叉树中结点的特定序列。这些线性序列中的每一个元素都有且仅有一个前驱结点和后继结点。
但是当我们希望得到二叉树中某一个结点的前驱或者后继结点时,普通的二叉树是无法直接得到的,只能通过遍历一次二叉树得到。每当涉及到求解前驱或者后继就需要将二叉树遍历一次。
为了节省时间,于是就有了线索二叉树,,线索二叉树实质上是使用了原本浪费的2*n-(n+1)=n-1个结点的指针来进行链接,使得我们将树这种一对多的逻辑结构按照遍历的序列改变成了一种一对一的线性结构,叫作线索链表。
5.5 树和二叉树的转换
简单来说,在将树转换成二叉树时,我们可以将一个结点A的所有子树集合认为是在左结点下的子树,而A的双亲结点其它子树的集合则是右结点下的子树,递归建树。
在将二叉树转换成树时,则是维持左结点与双亲结点的链接,但是将左结点的右结点全部都重新与双亲结点链接,还原成森林。
5.6 二叉树的应用
最典型的应用无非是哈夫曼树和哈夫曼编码。众所周知,我们传递更长信息的时间要比传递短信息的时间长,而计算机中只有0和1,那么为了让通信更加迅速,我们希望词频更高的词需要的编码更少,直接放一道例题
代码
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
using namespace std;
const int N = 1e6 + 100;
const int mod = 1e9+7;
int qpow(int a,int b) {
int ans=1,base=a;
while(b){
if (b&1) ans=ans*base;
base=base*base;
b>>=1;
}
return ans;
}
struct tree{
int value;
int weight;
int l;
int r;
int u;
}temp[N];
map<int,int>mp;
int pos;
bool f;
int x1,x2;
void choose(int end,int &x1,int &x2)
{
int min1=mod,min2=mod;
for(int i=1;i<=end;i++)
{
if(temp[i].weight<min1&&temp[i].u==0)
{
min1=temp[i].weight;
x1=i;
}
}
for(int i=1;i<=end;i++)
{
if(temp[i].weight<min2&&temp[i].u==0&&x1!=i)
{
min2=temp[i].weight;
x2=i;
}
}
}
void find(int t,int n,string p,bool f)
{
if(temp[n].value==t)
{
f=1;
cout<<p;
return;
}
if(temp[n].l==0&&temp[n].r==0)
return;
find(t,temp[n].l,p+'0',f);
if(f) return;
find(t,temp[n].r,p+'1',f);
if(f) return;
}
void solve()
{
string x;
cin>>x;
for(int i=0;x[i];i++)
{
mp[x[i]-'a'+1]++;
}
pos=1;
for(auto it:mp)
{
temp[pos].value=it.first;
temp[pos++].weight=it.second;
}
for(int i=pos;i<=2*pos-3;i++)
{
choose(i-1,x1,x2);
temp[i].l=x1;
temp[i].r=x2;
temp[i].weight=temp[x1].weight+temp[x2].weight;
temp[x1].u=i;
temp[x2].u=i;
}
/* for(int i=1;i<=pos*2-3;i++)
{
cout<<i<<endl;
cout<<"l:"<<temp[i].l<<endl;
cout<<"r:"<<temp[i].r<<endl;
cout<<"u:"<<temp[i].u<<endl;
cout<<"v:"<<(char)(temp[i].value+'a'-1)<<endl;
cout<<"w:"<<temp[i].weight<<endl;
cout<<endl;
}*/
for(int i=0;x[i];i++)
{
string p;
f=0;
find(x[i]-'a'+1,pos*2-3,p,f);
}
cout<<endl;
memset(temp,0,sizeof temp);
mp.clear();
}
signed main()
{
int t;
cin>>t;
while(t--)
{
solve();
}
}
n个叶子结点的哈夫曼树共有2n-1个结点