第十四课数据结构——树12.0 树型结构(一)树的定义树是一种数据结构,它是由n(n>=1)个有限结点组成一个具有层次逻辑关系的集合。
把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
它具有以下的特点:(1)每个结点有零个或多个子结点;(2)每一个子结点只有一个父结点;(3)没有前驱的结点为根结点;(4)除了根结点外,每个子结点可以分为m个不相交的子树;(二)树的有关术语(1)节点的度:一个节点含有的子树的个数称为该节点的度;(2)叶节点或终端节点:度为零的节点称为叶节点;(3)非终端节点或分支节点:度不为零的节点;(4)双亲节点或父节点:若一个结点含有子节点,则这个节点称为其子节点的父节点;(5)孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点;(6)兄弟节点:具有相同父节点的节点互称为兄弟节点;(7)树的度:一棵树中,最大的节点的度称为树的度;(8)节点的层次:从根开始定义起,根为第0层,根的子结点为第1层,以此类推;(9)树的高度或深度:树中节点的最大层次;(10)堂兄弟节点:双亲在同一层的节点互为堂兄弟;(11)节点的祖先:从根到该节点所经分支上的所有节点;(12)子孙:以某节点为根的子树中任一节点都称为该节点的子孙。
(13)森林:由m(m>=0)棵互不相交的树的集合称为森林;(三)树的物理存储一般使用数组来线性存储树中各节点的数据本身,但为了记录各节点之间的父子关系,需要附加存储父亲或孩子节点所在的位置。
1、双亲表示法:可以存储为:1 2 3 4 5 6 7 8 9 10 11优点:(1)节省空间。
(2)便于从下向上访问(记录了从孩子到父亲的逻辑关系)。
(3)便于随时插入新的子树。
缺点:不利于从上向下的访问。
(未记录父亲到孩子的逻辑关系)。
例如:利用所给边集创建双亲表达树输入:第一行两个整数n,m,表示节点个数和边的条数下面m行,每行2个整数,表示两个节点存在父子关系输出:如上的双亲表示法的树program maketree; //时间复杂度O(nlogn)const maxn=12;var inf,outf:text;bian:array[1..maxn,1..2]of integer; //存储边集tree,num:array[1..maxn]of integer; //存储树、各节点的度t:array[1..maxn]of boolean; //哈希n,m,i,j,k:integer;///////////////////////////////////////////////procedure init;beginassign(inf,'maketree.in'); assign(outf,'maketree.out');reset(inf);readln(inf,n,m);for i:=1 to m dobeginreadln(inf,bian[i,1],bian[i,2]);inc(num[bian[i,1]]); inc(num[bian[i,2]]); //统计各节点的度end;end;///////////////////////////////////////////////procedure make;begini:=1; k:=0;fillchar(t,sizeof(t),true);while k<m dobeginwhile num[i]<>1 do i:=i mod n+1; //查找度为1的节点j:=1;while not(t[j]and((bian[j,1]=i)or(bian[j,2]=i))) do inc(j); //找到包含该if j>n then break; //节点的边t[j]:=false;if bian[j,1]=i then tree[i]:=bian[j,2];if bian[j,2]=i then tree[i]:=bian[j,1];dec(num[bian[j,1]]); dec(num[bian[j,2]]);inc(k);end;end;///////////////////////////////////////////////procedure print;beginrewrite(outf);for i:=1 to n do write(outf,i:3); writeln(outf);for i:=1 to n do write(outf,tree[i]:3);close(outf);end;///////////////////////////////////////////////begininit;make;print;end.2、孩子表示法:(一般二叉树使用)1 2 3 4 5 6 7 8 9 10 11优点:(1)便于从上向下的访问。
(2)便于随时删除子树。
缺点:占用空间过大且不确定(使用链表时例外)。
3、混合表示法:既记录双亲关系又记录孩子关系。
12.1 树的应用(一)并查集并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。
常常在使用中以森林来表示。
并查集的存储一般采用双亲表示法。
一般为了提高使用效率,会附加储存一些信息,如:该节点下属节点个数、该节点到达根的距离等。
1、基本操作:一开始时,所有元素都分属于各个独立的集合。
(1)查找某结点的根:function get_father(x:integer):integer; //传入待查找的结点序号beginwhile father[x]<>0 do x:=father[x];get_father:=x;end;(2)合并两个不相交的集合:procedure join(x,y:integer);var xx,yy:integer;beginxx:=get_father(x); yy:=get_father(y);if xx<>yy then father[xx]:=yy;end;(3)判断两个结点是否属于一个集合:function same(x,y:integer):boolean;beginif get_father(x)=get_father(y) then same:=trueelse same:=false;end;2、并查集的优化:并查集在使用时,一旦结点多起来,就有可能退化成为一条链,这时,查找结点的祖先或判断两个结点是否是同一集合的复杂度就会称为O(n)。
(1)合并时将元素所在深度低的集合合并到元素所在深度高的集合。
(附加存储集合中根的深度rank[i])procedure join_rank(x,y:integer);var xx,yy:integer;beginxx:=get_father(x);yy:=get_father(y);if rank[xx]>rank[yy] then father[yy]:=xxelse father[xx]:=yy;if rank[xx]=rank[yy] then inc(rank[yy]);end;(2)路径压缩我们找到最久远的祖先时“顺便”把它的子孙直接连接到它上面。
function get_father(x:integer):integer;var xx,y:integer;beginxx:=x;while father[xx]<>0 do xx:=father[xx];while father[x]<>xx dobeginy:=father[x];father[x]:=xx;x:=y;end;end;3、例题:(1) 亲戚(Relations)或许你并不知道,你的某个朋友是你的亲戚。
他可能是你的曾祖父的外公的女婿的外甥的表姐的孙子。
如果能得到完整的家谱,判断两个人是否亲戚应该是可行的,但如果两个人的最近公共祖先与他们相隔好几代,使得家谱十分庞大,那么检验亲戚关系实非人力所能及.在这种情况下,最好的帮手就是计算机。
为了将问题简化,你将得到一些亲戚关系的信息,如同Marry和Tom是亲戚,Tom和B en是亲戚,等等。
从这些信息中,你可以推出Marry和Ben是亲戚。
请写一个程序,对于我们的关心的亲戚关系的提问,以最快的速度给出答案。
输入格式输入由两部分组成。
第一部分以N,M开始。
N为问题涉及的人的个数(1 ≤ N ≤ 20000)。
这些人的编号为1,2,3,…,N。
下面有M行(1 ≤ M ≤ 1000000),每行有两个数ai, bi,表示已知ai和bi是亲戚.第二部分以Q开始。
以下Q行有Q个询问(1 ≤ Q ≤ 1 000 000),每行为ci, di,表示询问ci和di是否为亲戚。
对于每个询问ci, di,若ci和di为亲戚,则输出Yes,否则输出No。
样例输入与输出输入relation.in10 72 45 71 38 91 25 62 333 47 108 9输出relation.outYesNoYes(2) 银河英雄传说【问题描述】公元五八○一年,地球居民迁移至金牛座α第二行星,在那里发表银河联邦创立宣言,同年改元为宇宙历元年,并开始向银河系深处拓展。
宇宙历七九九年,银河系的两大军事集团在巴米利恩星域爆发战争。
泰山压顶集团派宇宙舰队司令莱因哈特率领十万余艘战舰出征,气吞山河集团点名将杨威利组织麾下三万艘战舰迎敌。
杨威利擅长排兵布阵,巧妙运用各种战术屡次以少胜多,难免恣生骄气。
在这次决战中,他将巴米利恩星域战场划分成30000列,每列依次编号为1, 2, …, 30000。
之后,他把自己的战舰也依次编号为1, 2, …, 30000,让第i号战舰处于第i列(i = 1, 2, …, 30000),形成“一字长蛇阵”,诱敌深入。
这是初始阵形。
当进犯之敌到达时,杨威利会多次发布合并指令,将大部分战舰集中在某几列上,实施密集攻击。
合并指令为M i j,含义为让第i号战舰所在的整个战舰队列,作为一个整体(头在前尾在后)接至第j号战舰所在的战舰队列的尾部。
显然战舰队列是由处于同一列的一个或多个战舰组成的。
合并指令的执行结果会使队列增大。
然而,老谋深算的莱因哈特早已在战略上取得了主动。
在交战中,他可以通过庞大的情报网络随时监听杨威利的舰队调动指令。
在杨威利发布指令调动舰队的同时,莱因哈特为了及时了解当前杨威利的战舰分布情况,也会发出一些询问指令:C i j。
该指令意思是,询问电脑,杨威利的第i号战舰与第j号战舰当前是否在同一列中,如果在同一列中,那么它们之间布置有多少战舰。