其他分享
首页 > 其他分享> > 树型结构(一):树的概念

树型结构(一):树的概念

作者:互联网

文章目录

一、树的定义

树(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