树型结构(一):树的概念
作者:互联网
文章目录
一、树的定义
树(Tree)是n(n>=0)个结点的有限集。n=0时称为空树。
在任意一颗非空树中:
(1)每个元素称为结点(node);
(2)有且仅有一个特定的称为根(Root)的结点。
(3)当 n>1 时,其余结点可分为 m(m>0)个互不相交的有限集 T1、T2、…、Tm,
其中每一个集合本身又是一棵树,称为根的子树(SubTree)。
如图就是一颗典型的树:
下面的图明显不符合互不交互的原则(不能有多个爸爸啊),所以不是树。
二、树的基本概念
(1)树是递归定义的。
(2)一棵树至少有一个结点即根节点,它没有前驱,其余每个节点都有唯一的一个前驱。
每个结点可以有0个或多个后继结点。
(3)一个结点的子树个数称为这个结点的度(degree),一颗树的度是树内各结点度的最大值。
度为0的结点称为叶节点(leaf),度不为0的结点称为分支结点,根以外的分支结点又称内部结点。
(4)在用图形表示的树型结构中,对于两个相连接的结点,称上端结点为下端结点的父亲,称下端结点为上端结点的孩子。
称同一个父亲的多个孩子为兄弟,称从根节点到摸个子结点经过的所以点位该子结点的祖先,称以某个结点为根的子树中的
任一结点为该结点的子孙。如上图,结点 A 是结点 B、C 的父亲,结点 B 和结点 C 是兄弟,结点 B 是结点 D、G、I的祖先。
(5)定义一棵树的根结点的层次(level)为0,其他结点的层次等于它的父结点层次加1。
一棵树的深度(Depth)或高度是树中所以结点的层次的最大值。
(6)对于树中任意两个不同的结点,如果从一个结点出发,自上而下沿着树的线段能够到达另一结点,称它们之间存在路径。
路径的长度等于路径经过的结点数减1。如上图,从 B 到 I 存在路径,表示为(B、D、I),长度为2。
不同子树的结点不存在路径,从根结点出发到某一结点一定存在路径。
(7)森林(forest)是 m(m>=0)颗互不相交的树的集合。
三、树的存储结构
【方法一】“父亲表示法”
struct node
{
int data,father;
}tree[101];
缺点:很难找儿子。
【方法二】“孩子表示法”
单链表结构,每个结点包括一个数据域和一个指针域,指向若干子结点。
typedef struct node;
typedef node *tree;
struct node
{
int data;
tree child[101];
}
tree t;
缺点:只能从父结点遍历到子结点,不能返回到父结点。
【方法三】“父亲孩子表示法”
树型双链表结构,每个结点包括一个数据域和两个指针域,一个指向父亲,一个指向孩子。
typedef struct node;
typedef node *tree;
struct node
{
int data;
tree father[101];
tree child[101];
}
tree t;
【方法四】“孩子兄弟表示法”
二叉树表示法,双链表结构,每个结点包括一个数据域和两个指针域,
一个指向该结点的第一个孩子结点,一个指向该结点的下一个兄弟结点。
typedef struct node;
typedef node *tree;
struct node
{
int data;
tree firstchild,brother;
}
tree t;
【问题描述】
给定一棵树,输出树根root,孩子最多的结点maxn以及它的孩子。
【样例输入】
8 7 //结点个数和边数
4 1//结点x、y,y是x的孩子
4 2
1 3
1 5
2 6
2 7
2 8
【样例输出】
4 //root
2 //maxn
6 7 8
【程序实现】
#include<iostream>
using namespace std;
int father[101];
int main()
{
int n,m,x,y,root=0,maxn=0,maxchild=0;
cin>>n>>m;
for(int i=1;i<=m;i++)
{
cin>>x>>y;
father[y]=x;//y的父亲是x
}
for(int i=1;i<=n;i++)//找根结点
{
if(father[i]==0)//没有父亲的结点
{
root=i;
break;
}
}
for(int i=1;i<=n;i++)//找孩子最多的结点
{
int sum=0;
for(int j=1;j<=n;j++)
if(father[j]==i)sum++;
if(sum>maxn)
{
maxn=sum;
maxchild=i;
}
}
cout<<root<<endl;
cout<<maxchild<<endl;
for(int i=1;i<=n;i++)
if(father[i]==maxchild)
cout<<i<<" ";
cout<<endl;
return 0;
}
四、树的遍历
【先序遍历】
先访问根结点,再从左往右按照先序思想遍历各子树。
上图的先序遍历为:ABDGHICEFJ;
【后序遍历】
先从左往右遍历子树,再访问根结点。
上图的后序遍历为:GHIDBJEFCA;
【层次遍历】
按层次从小到大逐个访问,同一层次按从左往右的次序。
上图的层次遍历为:ABCDEFGHIJ;
【叶结点遍历】
有时把所有的数据信息存放在叶结点中,其余结点不是数据之间的关系时,采用这种方法遍历。
上图的叶结点遍历为:GHIJF。
【实现】
可以看出,A、B两种方法是递归定义的,可以用“深度优先搜索”的思想。
void travel(tree t,int m)
{
if(t)
{
cout<<t->data<<endl;
for(int i=0;i<m;i++)
travel(t->child[i],m);
}
}
而 C 方法实际上就是我们的“广度优先搜索”。顺序访问每一个结点的子结点,入队,再出队,直到队空。
tree p;
tree q[101];//队列
int head=1,tail=1;
void work()
{
q[1]=t;
tail++;
while(head<tail)
{
p=q[head];
head++;
cout<<p->data<<endl;
for(int i=0;i<m;i++)
{
if(p->child[i])//找到子结点
{
q[tail]=p->child[i];//入队
tail++;
}
}
}
}
单词查找树
【问题描述】
在进行文法分析的时候,通常需要检测一个单词是否出现在我们的单词列表。
为了提高查找和定位的速度,通常会画出与单词列表对应的单词查找树,其特点如下:
(1)根结点不包含字母,除根节点外每一个节点都只包含一个英文字母。
(2)从根结点到某一结点,路径上经过的字符连接起来,为该结点对应的单词。
(3)在满足上述条件下,该单词查找树的结点数最少。
对于一个单词列表,请统计对应单词查找树的结点数。
【格式】
输入为一个单词列表,每一行仅包含一个大写单词和一个换行符。
单词长度不超过63个字母,文件总长度不超过32K,至少有一行数据。
【样例输入】
A
AN
ASP
AS
ASC
ASCII
BAS
BASIC
【样例输出】
13
【算法分析】
假设我们已经建立一棵树,对于当前处理的单词,每一步都存在两种情况,
首先,在根结点的子结点中找单词的首字母,若存在,进而在该子结点找单词的下一位……
如此下去直到单词查找结束,即不需要在树中添加新结点;
如果单词的第 n 为没有被找到,则把单词的第 n 为及其后的字母依次加入单词查找树中。
这是建树的过程,但问题只需要我们结点数,且有32K文件,考虑能不能不建树计算出结点数。
为了更好的解释,我们定义:
一个单词相当于另一个单词的差:设单词1长度为L,且与单词2从第N位开始不一致,则单词1相对于单词2的差为 L-N+1。
这是描述单词相似程度的量。可见,将一个单词加进单词树时,须加入的结点数等于该单词树中已有单词的差的最小值。
而字典序就是使用这样的方式进行排序,第m个单词对于第m-1个单词的差一定是它对于前m-1个单词的差中最小的。
于是得出算法:
①对单词进行字典序排序;
②依次计算每个单词对于前一个单词的差,进行累加;
③累加的和再加1,因为有根结点。
注意,第一个单词的差为它自身长度。
根据样例,我们可以得到下面的表格:
原单词列表 | 排序后的单词列表 | 差 |
---|---|---|
A | A | 1 |
AN | AN | 1 |
ASP | AS | 1 |
AS | ASC | 1 |
ASC | ASCII | 2 |
ASCII | ASP | 1 |
BAS | BAS | 3 |
BASIC | BASIC | 2 |
累加差值在加1就是答案13。
【数据结构】
先确定 32K(32×1024=32768 字节)的文件最多有都是单词和字母。单词越短就能存放越多。
长度为1的单词最多有26个,长度为2的单词最多有(262=676 个),换行符占2个字节。
所以目前总共占用(1+2)× 26+(2+2)× 676 = 2782 字节,剩余(32768-2782=29986 字节)分配给长度为3的单词,
长度为3的单词最多有(263=17576 个),而最大剩余情况下还可以存 29986/(3+2) = 5997 个。
所以,最多有 26+676+5997=6699 个单词。定义数组:string a[7000]。
【程序实现】
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
int i,j,k,n,sum=1;
string a[7000];
string s;
int main()
{
while(cin>>a[++n]);//读入
n--;
sort(a+1,a+n+1);//在没有指定排序方法时默认字典序排序
sum+=a[1].length();
cout<<a[1].length()<<endl;//测试
for(i=2;i<=n;i++)
{
j=0;
while((a[i][j]==a[i-1][j])&&(j<a[i-1].length()))j++;
cout<<a[i].length()-j<<endl;//测试
sum+=a[i].length()-j;
}
cout<<sum<<endl;
return 0;
}
标签:node,结点,遍历,int,tree,单词,概念,树型,结构 来源: https://blog.csdn.net/Alex_Colon/article/details/99818778