树的存储(孩子表示法)

这篇博客详细介绍了使用孩子表示法存储树的结构,包括如何存储树、查找孩子和父元素、添加与删除元素的算法。文章指出,虽然查找孩子元素的时间复杂度为O(1),但查找父元素需要遍历整个数组,时间复杂度较高。同时,提出了双亲孩子表示法可以优化查找效率。博客还提供了C语言的代码实现来演示这些操作。
摘要由CSDN通过智能技术生成

我们要存储的树是:

 真实存储在内存中的结构是:

 

找孩子元素相当方便,遍历某个元素数组后面的链表,即可获得该结点的所有孩子元素。

但是找父亲元素麻烦点,需要遍历整个数组,而且每个数组的每个链表都需要遍历。时间复杂度就上去了。

删除元素需要递归删除(别忘了free掉链表结点占用的存储空间),删除之后直接data域直接置为' ',child指针域直接置为null.没有采用那种删除掉一个元素之后数组中最后一个数据前移的操作。因为 数组中的相对顺序还是挺重要的,就怕那种有孩子的元素,孩子元素移动了位置,自己还不知道,导致父元素访问子元素的内容出问题。

递归删除完成后,别忘了修改一下该元素父元素的链表域,从链表域中剔除掉该元素的数组下标。

添加元素添加完成后,别忘了在父元素的链表后面添加一个结点,存储这个新添加元素的数组下标。

代码及结果测试:

#include<stdio.h>
#include<stdlib.h>
/*
	树的存储 (孩子表示法)
	 	此种存储方式找孩子结点方便(遍历单个链表即可),但是找父亲结点不方便。
	 	需要遍历整个数组,每个数组的链表都需要遍历。
	 	插入的话,数组中需要占一个空间存储该元素,插入此元素的父结点还得需要申请一个结点去存储新插入元素的下标。
	 	这就涉及到找父节点了。时间复杂度较高。
	 	删除的话。特别简单,删除当前元素时,将当前元素的链表里面的所有元素依次递归删除即可。
 		(删除之后直接置空就行,不能让数组的尾部元素去占领被删除元素的位置)(因为我们链表里面存储的是数组下标,对位置有要求。)	 
*/
#define ElemType char 
#define MaxSize 20 
#define null NULL 

 //链表结点 
 typedef struct LNode{
 	int child;        //孩子结点所在数组的位置下标。
	struct LNode *next; 
 }LNode;
 
 //数组结点
 typedef struct ANode{
 	ElemType data;  //存放数据元素
	struct LNode *child; 
 }ANode; 
 
 
 //树的存储最终结构体 
 typedef struct PTree{
 	ANode nodes[MaxSize];  //存放树中所有结点 
 	int n;   //树中的结点数  
 }PTree;
 
 void deleteRecursion(PTree *ptree,ANode *anode);
 
 
 //用(孩子表示法)存储一棵普通的树 
 void initPtree(PTree *ptree){
 	printf("请您输入结点的数量:\n");
 	scanf("%d",&(ptree->n));
 	for(int i = 0 ;i< ptree->n ;i++){
	 	printf("输入第%d个结点的值:\n",i+1);
	 	getchar();
	 	scanf("%c",&(ptree->nodes[i].data));
	 	printf("输入结点%c的孩子结点数量:\n",ptree->nodes[i].data);
		int num;
		scanf("%d",&num);
		//m指针指向最后一个元素
		LNode *m = null;
		for(int j = 0 ; j < num ;j++){
			LNode * s = (LNode *)malloc(sizeof(LNode));
			printf("请输入第%d个孩子结点在顺序表中的位置:\n",j+1);
			scanf("%d",&(s->child));
			s->next = null;
			//第一个结点的操作跟别的结点不太一样 
			if(j == 0){
				 ptree->nodes[i].child = s;
				 m=s;
			}else{
				m->next=s;
				m=s;	
			}		
		}
	 	
	 }
 	
 } 
 //找孩子结点 
 void findChild(PTree *ptree){
 	ElemType ch;
 	printf("请输入您想查找哪个元素的孩子结点\n");
 	getchar();
 	scanf("%c",&ch);
 	int index = -1;
 	for(int i = 0;i< ptree->n ; i++){
 		if(ptree->nodes[i].data == ch){
		 	index = i;
		 	break;
		 }	
 	}
 	if(index == -1){
	 	printf("您要查找的元素不存在\n");
	 	return;
	 }
	 //遍历输出孩子结点
	 LNode *p = ptree->nodes[index].child;
	 if(p == null){
 		printf("此节点为叶子结点\n");
 		return;
	}
	 while(p != null){
 		printf("孩子结点有%c\n",ptree->nodes[p->child].data);
 		p=p->next;
 	} 
 }
 
 //找父亲结点
 void findParent(PTree *ptree){
	ElemType ch;
 	printf("请输入您想查找哪个元素的父亲结点\n");
 	getchar();
 	scanf("%c",&ch);
	int index = -1;
 	for(int i = 0;i< ptree->n ; i++){
 		if(ptree->nodes[i].data == ch){
		 	index = i;
		 	break;
		 }	
 	}
 	if(index == -1){
	 	printf("您要查找的元素不存在\n");
	 	return;
	 }
	 //遍历整个数组以及数组中的链表,看哪个里面有这个元素。
	 for(int i = 0; i < ptree->n ; i++){
 		LNode *p = ptree->nodes[i].child;
 		while(p != null){
			if(p->child == index){
				printf("%c的父亲结点是%c,所在的位置下标是%d\n",ch,ptree->nodes[i].data,i);
				return;
			}
			p=p->next;
	 	}
 	}
 	//遍历了一遭没遍历到, 则此节点为根节点,不存在父亲结点。
 	printf("您要遍历的结点是根结点,没有父亲结点\n"); 
 }
 
 //插入一个结点 
 void addANode(PTree *ptree){
	ElemType ch;
	ElemType ne;
 	printf("您要插入的元素是,您想把他插入到哪个元素的后面\n"); 
 	getchar();
 	scanf("%c %c",&ch,&ne);
 	int index = -1;
 	int father = -1;
 	//找到新元素要插入的位置 
 	for(int i = 0;i < ptree->n; i++){
 		//代表这个位置没有存储元素
	 	if(ptree->nodes[i].data == ' ' && ptree->nodes[i].child	== null){
			index =	i;
			break;
	 	}
	 }
	 //找到父亲结点,将新元素的数组下标链接到父亲结点链表末尾。 
	 for(int i =0; i < ptree->n ;i++){
 		if(ptree->nodes[i].data == ne){
	 		father = i;
	 		break;
	 	}
 	}
	 
	 if(index == -1){
 		if(ptree->n >= MaxSize){
 			printf("数组中没有多余的位置,尚不能添加新的元素\n");
 			return;
	 	}
	 	ptree->nodes[ptree->n].data = ch;
	 	index = ptree->n;
	 	ptree->n++;
	 	 
 	}
	 //将新元素存储到这个位置
	 ptree->nodes[index].data = ch;
	 //改变父结点中链表元素个数
	 LNode *p = ptree->nodes[father].child;
	 LNode *s = (LNode *)malloc(sizeof(LNode));
	 s->child=index;
	 s->next=null;
	 if(p == null){
 		ptree->nodes[father].child=s;
 	}else{
 		//在最后一个位置插入 
	 	while(p->next != null){
		 	p=p->next;	
		 }
		 p->next= s;
	 }	
 } 
 
 //删除一个结点(递归删除该结点所有子结点)
 void  deleteANode(PTree *ptree){
	ElemType ch;
 	printf("请输入您要删除的元素是\n"); 
 	getchar();
 	scanf("%c",&ch);
	int index = -1;
 	for(int i = 0 ;i < ptree->n; i++){
	 	if(ch == ptree->nodes[i].data){
		 	index = i;
		 	break;
		 }
	 }
	 if(index == -1){
 		printf("您要删除的元素不存在\n");
	 	return;	
 	}
 	//删除的元素没有孩子结点 
 	if(ptree->nodes[index].child == null){
 		printf("您已经将%c结点删除\n",ptree->nodes[index].data);
 		ptree->nodes[index].data = ' '; 
	 }
	 //删除的元素有孩子结点,如果孩子结点仍然有孩子结点,也要将其删除。 
	 deleteRecursion(ptree,&(ptree->nodes[index])); 
	 //找父元素, 找到父亲元素之后,循环遍历链表,删除此元素的数组位置下标。 (这就是链表的删除操作) 
	 for(int i = 0; i < ptree->n ; i++){
		LNode *p = ptree->nodes[i].child;
		//如果要删除的是第一个结点 
		if(p != null && p->child == index){
			ptree->nodes[i].child = p->next;
			free(p);
			return;
		}
		while(p->next != null){
			if(p->next->child == index){
				LNode *q = p->next;
				p->next = q->next;
				free(q);
				return;
			}
			p=p->next;
		}	
 	}
	 
 } 
 
 //递归删除某个结点的子结点 
 void deleteRecursion(PTree *ptree,ANode *anode){
 	LNode *p = anode -> child ;
 	while(p != null){
 		//递归删除孩子的孩子 
	 	deleteRecursion(ptree,&(ptree->nodes[p->child]));
	 	LNode *s = p;
		 p=p->next; 
		 free(s); 
 	}
	//删除完了链表结点,我们再来处理数组结点 
	anode->data =' ';
	anode->child = null; 		
 }

 //打印 数组+链表中的值 
 void printContent(PTree *ptree){
 	for(int i =0;i<ptree->n;i++){
 		printf("%c ",ptree->nodes[i].data);
 		LNode *p = ptree->nodes[i].child;
	 	while(p!=null){
	 		printf("%d ",p->child);
	 		p=p->next;
	 	}
	 	printf("\n");
	 }
 } 
 
int main(int argc, char *argv[])
{

	PTree ptree;
	//最初的初始化  data = ' '  child =  null 
	for(int i =0 ;i<MaxSize;i++){
		ptree.nodes[i].data = ' ';
		ptree.nodes[i].child = null;	
	}
	initPtree(&ptree);
	//findChild(&ptree);
	//findParent(&ptree);
	
	//printContent(&ptree);
	//addANode(&ptree);
	printContent(&ptree);
	deleteANode(&ptree); 
	printContent(&ptree);
	
	addANode(&ptree);
	printContent(&ptree); 
	return 0;
}

注意: 

        1.树的双亲表示法:查找父元素的时间复杂度为O(1).

        2.树的孩子表示法,查找孩子元素的时间复杂度为O(1).

我们还可以创建一种数据结构, 双亲孩子表示法.这样找孩子结点的时间复杂度为O(1).找父亲结点的时间复杂度也是O(1).

删除操作也要比单纯的孩子表示法时间复杂度低很多。

基本操作代码就是本章和上一章代码的部分结合。(思路还是那个思路)。

双亲孩子表示法存储在内存中是:

 

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值