树

树:树(Tree)是 n(n >= 0) 个节点的有限集。
    n=0时称为空树,在任意一棵空树中:
         ①有且仅有一个特定的被称为根(Root)的节点;
         ②当n>=1时,其余节点可以分为m(m>0)个互不相交的有限集 
    即:树的节点分为:根节点和叶节点,根节点没有前驱结点,叶节点没有后继结点 
    
   PS: ①树的根节点是唯一的
          ②子树的个数没有限制,但是他们一定是互不相交的
          ③树的定义具有递归性,即“树中还有树 ”  
          ④对于只有一个节点的树,那个节点既是根节点又是叶节点 


节点

节点的度:节点拥有的子树个数称作节点的度
节点的分类:   ①叶节点(Leaf):度为0的节点
                      ②非终端节点/分支节点:度不为0的节点
            
    PS:树的度是树内各节点的度的最大值


节点间的关系: 

             ①双亲关系:节点的子树的根称为该节点的孩子(Child),即该节点为孩子的双亲(Parent) 
             ②兄弟关系:同一个双亲的孩子之间是兄弟关系(Sibling)
             ③祖先关系:从树根到该节点所经分支上的所有节点都是该节点的祖先(Ancestor) 


节点的层次(Level):从根开始算起,根为第一层,根的孩子为第二层,以此类推
PS:  ①双亲在同一层的节点互为堂兄弟
        ②树中节点的最大层次被称为树的深度或高度(方向向下数为深度,方向向上数为高度)
        ③如果树中节点的各子树有序,不能互换,则该树为有序树,否则称为无序树 

注:森林(Forest)是m (m >= 0)棵互不相交的树的集合 


树的存储结构

 1.双亲表示法

                            表示方法:结构体数组 
                            数据域:存储节点的值
                            双亲指针:存储父节点在数组中的下标
        优点:存储高效、查找父节点迅速、适合静态树
        缺点:查找子节点低效、插入/删除复杂、不支持森林

双亲表示法代码(C++):

#include <iostream>
using namespace std;

//父亲表示法(数组存树)
//初始化树节点结构 
struct Node{
	char data;//数据域
	int f_i;//父亲节点的下标 
}t[105]; 
int size;//树中节点的个数

//初始化树的根节点
void initTree(char x){
	t[0].data = x;
	t[0].f_i = -1;//根节点没有父亲
	size++; 
}

//寻找节点fx并返回其下标
int Find(char fx){
	//遍历整个树寻找
	for(int i=0;i<size;i++){
		//查找成功
		if(t[i].data == fx){
			return i;
		} 
	}
	return -2;//该树中不存在节点fx 
} 
//插入节点x到树中
void Insert(char x,char fx){
	t[size].data = x;//更新节点数据域 
	
	int fx_i = Find(fx);//查找父亲节点下标 
	t[size].f_i = fx_i;//更新节点x的父亲下标
	 
	size++;//树中节点个数+1 
} 

//输出树中节点
void Print(){
	for(int i=0;i<size;i++){
		cout<<t[i].data<<" ";
	}
	cout<<endl;
}

int main(){
	//建树 
	int n;//树中节点个数 
	char root;//根节点 
	cin>>n;
	cin>>root;
	
	initTree(root);
	char x,fx;
	for(int i = 1;i<=n-1;i++){
		cin>>x>>fx;
		Insert(x,fx); 
	}
	Print();
	cout<<endl;
	
	initTree(root);
	char x,fx;
	for(int i = 1;i<=n-1;i++){
		cin>>x>>fx;
		Insert(x,fx); 
	}
	Print();
	cout<<endl;
	
	//找x的父亲和孩子
	cin>>x;
	int x_i=Find(x);//查找该节点在树中的下标 
	
	//确保节点存在 
	if(x_i!=-2){
		//找父亲
		int fx_i = t[x_i].f_i;//节点x的父亲节点下标 
		//非根节点 
		if(fx_i!=-1){
			cout<<"该节点的父亲节点是:"<<t[fx_i].data<<endl;
		}
		//根节点 
		else{
			cout<<"该节点是根节点,没有父亲节点";
		}
		
		
		//找孩子
		int cnt = 0;//记录节点x的度
		for(int i=0;i<size;i++){
			if(t[i].f_i==x_i){
				if(cnt==0){
					cout<<"该节点的孩子节点是:";
				}
				cout<<t[i].data<<" ";
				cnt++;
			}
		}
		if(cnt==0){
			cout<<"该节点是叶节点,没有孩子节点!"<<endl;
		}
	}
	else{
		cout<<"该节点不存在!"<<endl;
	}
	return 0;
}
/*
10
R
A R
B R
C R
D A
E A
F C
G F
H F
K F
R A B C D E F G H K

C
该节点的父亲节点是:R
该节点的孩子节点是:F
*/

 2.孩子表示法

                  结构体数组 + 链表(数组存节点数据及孩子链表表头,链表存孩子节点下标及该节点的下一个孩子节点的地址)


        优点:遍历子节点高效、支持动态插入/删除、适合多叉树
        缺点:查找父节点低效、内存开销较大、实现较为复杂

孩子表示法代码(C++):

#include <iostream>
using namespace std;

//孩子表示法(数组+链表存树)
//初始化孩子节点结构 
struct CHNode{
	int ch_i;//孩子节点下标
	struct CHNode* next;//该节点的下一个孩子节点 
};
//初始化树节点结构
struct Node{
	char data;//数据域
	CHNode* first;//指针域,指向第一个孩子 
}t[105]; 
int size;//树中节点的个数

//初始化树的根节点
void initTree(char root){
	t[0].data=root;//更新根节点数据域
	t[0].first=nullptr;//清空根节点指针域
	size++;//树中节点个数+1 
}

//寻找节点fx并返回其下标
int Find(char fx){
	//遍历整个树寻找
	for(int i=0;i<size;i++){
		//查找成功
		if(t[i].data == fx){
			return i;
		} 
	}
	return -2;//该树中不存在节点fx 
}
//插入节点x到树中
void Insert(char x,char fx){
	int fx_i=Find(fx);
	if(fx_i==-2){
		cout<<"节点"<<fx<<"不存在,插入失败!"<<endl;
		return;
	} 
	
	t[size].data=x;//更新节点数据域 
	t[size].first=nullptr;//节点指针域置空
	
	//将节点x存入fx的孩子链表中 
	CHNode* node = new CHNode();
	node->ch_i=size;
	//新孩子节点以头插方式加入孩子链表 
	node->next=t[fx_i].first;
	t[fx_i].first=node;
	
	size++;//节点个数+1
}

//找父亲
void Find_fa(int x_i){
	CHNode* p=nullptr;//指针p遍历孩子链表 
	bool flag = false;//表示是否找到父亲节点 
	
	//遍历每个节点的孩子链表寻找孩子下标与 x_i一致的下标
	for(int i=0;i<size;i++){
		p=t[i].first;
		while(p!=nullptr && p->ch_i!=x_i){
			p=p->next;
		}
		if(p!=nullptr && p->ch_i==x_i){
			flag=true;//表示节点x的父亲节点已经找到 
			cout<<"节点"<<t[x_i].data<<"的父亲节点是:"<<t[i].data<<endl;
			break; 
		}
	}
	if(!flag){
		cout<<"节点"<<t[x_i].data<<"为根节点,没有父亲节点!"<<endl;
	}
} 

//找孩子
void Find_ch(int x_i){
	CHNode* p=t[x_i].first;//指针p指向结点x的孩子链表
	int ch_i;//孩子节点下标
	
	if(p==nullptr){
		cout<<"节点"<<t[x_i].data<<"没有孩子节点!"<<endl;
		return;
	}
	
	//遍历节点x的孩子链表
	cout<<"节点"<<t[x_i].data<<"的孩子节点是:";
	while(p!=nullptr){
		ch_i=p->ch_i;
		cout<<t[ch_i].data<<" ";
		p=p->next; 
	} 
	cout<<endl;
}

//输出树中节点
void Print(){
	for(int i=0;i<size;i++){
		cout<<t[i].data<<" ";
	}
	cout<<endl;
}

int main(){
	//建树 
	int n;//树中节点个数 
	char root;//根节点 
	cin>>n;
	cin>>root;
	
	initTree(root);
	char x,fx;
	for(int i = 1;i<=n-1;i++){
		cin>>x>>fx;
		Insert(x,fx); 
	}
	Print();
	cout<<endl;
	
	//找x的父亲和孩子
	cin>>x;
	int x_i=Find(x);//查找该节点在树中的下标
	
	//确保节点存在
	if(x_i!=-2){
		//找父亲
		Find_fa(x_i);
		
		//找孩子
		Find_ch(x_i);
	}
	return 0;
}
/* 
10
R
A R
B R
C R
D A
E A
F C
G F
H F
K F
R A B C D E F G H K

C
节点C的父亲节点是:R
节点C的孩子节点是:F
*/

3.孩子兄弟表示法

                                表示方法:二叉链表 
                                数据域:存储节点的值
                                左指针:存孩子的引用
                                右指针:存兄弟的引用
        优点:内存高效、操作灵活、将任意多叉树转换为二叉树
        缺点:查找父节点低效(所以本文代码不实现该功能)

孩子兄弟表示法代码(C++):

#include <iostream>
using namespace std;

//孩子兄弟表示法
//初始化树节点结构
struct TreeNode{
	char data;//数据域
	struct TreeNode* left;//左指针存孩子
	struct TreeNode* right;//右指针存兄弟 
};
using TNode = TreeNode;

//初始化树的根节点
TNode* initTree(char root){
	TNode* node = new TNode();
	
	node->data=root;//更新根节点指针域 
	node->left=node->right=nullptr;//初始状态下左右指针均为空
	
	return node; 
}

//寻找节点fx并返回其下标(DFS搜索)
TNode* Find(TNode* root,char fx){
	if(root==nullptr) return root;//空树,直接返回
	if(root->data==fx) return root;//找到节点fx,返回
	
	//搜索左子树
	if(root->left!=nullptr){
		TNode *ans=Find(root->left,fx);
		if(ans!=nullptr&&ans->data==fx)return ans;
	}
	
	//搜索右子树
	if(root->right!=nullptr){
		TNode* ans=Find(root->right,fx);
		if(ans!=nullptr&&ans->data==fx)return ans;
	}
	
	return nullptr;//节点fx不存在,返回空
}

//插入节点x到树中
void Insert(TNode* root,char x,char fx){
	TNode *f=Find(root,fx);
	if(f==nullptr){
		cout<<"节点"<<fx<<"不存在,插入失败!"<<endl;
		return; 
	}
	
	//插入节点x做fx的第一个孩子节点(头插)
	TNode* node = new TNode();
	
	node->data=x;//更新节点x数据域
	node->left=nullptr;//节点x左指针(孩子指针)置空
	node->right=f->left;//将父亲节点的孩子信息更新到节点x的兄弟中 
	f->left=node;//更新父亲节点f的左指针(孩子指针)
}

//输出树中节点(DFS前序遍历:根->左->右)
void Print(TNode* root){
	if(root==nullptr) return;//空树,直接返回
	
	//输出当前节点 
	cout<<root->data<<" ";
	
	//遍历孩子
	Print(root->left);
	
	//遍历兄弟
	Print(root->right); 
}

int main(){
	//建树 
	int n;//树中节点个数 
	char r;//根节点 
	cin>>n;
	cin>>r;
	
	TNode* root=initTree(r);
	char x,fx;
	for(int i = 1;i<=n-1;i++){
		cin>>x>>fx;
		Insert(root,x,fx); 
	}
	
	//输出 
	cout<<"前序遍历输出树中节点如下:"<<endl;
	Print(root);
	cout<<endl;
	cout<<endl;
	
	//找x的孩子
	cin>>x;
	TNode *f=Find(root,x);//查找节点x 
	TNode *p=f->left;//指针p遍历查找节点x的孩子
	cout<<"节点"<<x<<"的孩子节点是:";
	while(p!=nullptr){
		cout<<p->data<<" ";
		p=p->right;
	} 
	cout<<endl;	
	
	return 0;
}
/* 
10
R
A R
B R
C R
D A
E A
F C
G F
H F
K F
前序遍历输出树中节点如下:
R C F K H G B A E D

F
节点F的孩子节点是:K H G
*/

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐