树的存储(双亲表示法)

我们要存储的树是:

双亲表示法存储在内存中的结构是:

 

此种方式找父节点好找,但是找子结点需要遍历整个数组。

删除某一个结点时:(思路)

        1.该结点没有孩子,直接删除即可。

        2.该结点有孩子,需要递归将孩子结点删除,然后再删除该结点。

删除时的小细节:

        1.每删除一个元素时,将数组中的最后一个元素前移,节省存储空间,遍历数组时方便,遍历的元素少,但是存在问题。(F结点有子结点,F的子结点在未删除的情况,F结点进行了移动,导致F结点的下标发生了变化,继而导致 GHK与F结点失联。)

        2.删除的时候,数组最后一个元素不再前移。而是在插入的时候,遍历数组,找到空闲位置然后进行插入。此种方式同样也不会造成数组存储空间的浪费。 而且不会出现孩子结点找不到父亲的情况。这种方式比前一种就是遍历数组找元素的时候,可能会多遍历几个元素。--> 但是从正确性的角度来说优于第一种。 此种方式n就不能代表数组中有效元素个数。

代码及结果截图:

#include<stdio.h>
#include<stdlib.h>
/*
	树的存储:	双亲表示法(本质上用数组实现)
				孩子表示法(数组+链表) 
				孩子兄弟表示法(用链表存储  左孩子右兄弟) 				
*/
#define ElemType char
#define null NULL
#define MaxSize 20
typedef struct PNode{
	ElemType data; //数据元素 
	int parent;   //双亲的位置 
}PNode; 

typedef struct PTree{
	PNode nodes[MaxSize]; //树的双亲表示 
	int n; 				  //结点数 
}PTree;

void deleteRecursionNew(PTree *ptree,int index);
void deleteRecursion(PTree *ptree,int index);


//用双亲表示法初始化一棵树 
PTree InitPTree(PTree ptree){
	ElemType ch;
	int j;
	printf("请输出你要创建的树的结点个数:\n");
	scanf("%d",&(ptree.n));
	printf("请输入结点的值,以及该结点双亲位于数组的下标:\n"); 
	for(int i = 0;i < ptree.n; i++){
		getchar();
		scanf("%c %d",&ch,&j);		
		ptree.nodes[i].data=ch;
		ptree.nodes[i].parent=j;
	}
	return ptree;
	
}

//找父结点(O(n)) -->找到该元素的时间复杂度为O(n),真正找父节点的时间复杂度为O(1) 
void findParent(PTree ptree){
	ElemType ch;
	printf("您要查找的是哪个元素的父结点\n");
	getchar();
	scanf("%c",&ch);
	int findStatus = 0;
	for(int i = 0 ; i < ptree.n ;i++){
		if(ptree.nodes[i].data == ch){
			if(ptree.nodes[i].parent == -1){
				printf("这是树根元素,此元素没有父亲\n");
				return;
			}
			//找到了
			printf("该元素是%c,他的父元素是%c\n",ch,ptree.nodes[ptree.nodes[i].parent].data);
			findStatus = 1;
			break; 
		}
	}
	if(findStatus == 0 ){
		printf("您要查找的元素不在这棵树中\n");
	}
}

//找子节点(O(2n))--> 找到该元素的时间复杂度为O(n),真正找到子节点的时间复杂度也为O(n). 
void findChild(PTree ptree){
	char 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++){
		if(ptree.nodes[i].parent == index){
			printf("该元素是%c,该元素的直接子结点有%c\n",ch,ptree.nodes[i].data);
		}
	}
} 

//添加一个结点 O(1)
void addPNode(PTree *ptree){
	ElemType ch;
	int j;
	printf("您要添加的结点的值,以及该结点双亲位于数组的下标:\n");
	getchar();
	scanf("%c %d",&ch,&j); 
	if(ptree->n>=MaxSize){
		printf("存储空间不足,插入元素失败"); 
		return;
	}
	ptree->nodes[ptree->n].data = ch;
	ptree->nodes[ptree->n].parent=j;
	(ptree->n)++; 
}





/*
	此种删除方式:虽然删除结点后,把数组中最后的元素移到当前位置。节省存储空间。删除A,B,C结点结果正确,元素前移也正确。
				但是删除R结点,按理说是删除所有结点,但是最后打印的数组中还剩下KGH 三个元素。
				经分析原因如下:
					1.删除A D E 元素时,GHK元素前移 顺序变为 G K H , 然后递归删除B结点, F元素前移。递归删除C元素。
					发现A有个孩子是F,于是递归删除F结点,再删除F结点时,F的下标已经变了,此时GHK的parent是6,而不是2.
					所以GKH元素未能进行递归删除。所以最后留下了GKH元素。
				本质是由于F结点有子结点,F的子结点在未删除的情况,F结点进行了移动,导致F结点的下标发生了变化,继而导致
				GHK与F结点失联。 
*/

//双亲表示法时,删除某个元素及其子元素 
void deletePNode(PTree *ptree){
	printf("请输入您要删除的元素是:\n");
	ElemType ch;
	getchar();
	scanf("%c",&ch);
	int index = -1;
	for(int i=0;i< ptree->n;i++){
		if(ptree->nodes[i].data == ch){
			index = i;	
			break;
		}
	}
	//递归删除子元素 
	deleteRecursion(ptree,index);
	
	printf("剩余的元素个数是%d\n",ptree->n); 
	//然后再删除此元素
	if(ptree->n > 0 && index != ptree->n-1){
		ptree -> nodes[index].data = ptree->nodes[(ptree->n)-1].data;
		ptree ->nodes[index].parent =  ptree->nodes[(ptree->n)-1].parent;
 		ptree->nodes[(ptree->n)-1].data = ' ';
 		ptree->nodes[(ptree->n)-1].parent= -2;
		(ptree->n)--;
	}else{
		//删除的是最后一个元素 或者数组中已经没有元素可以挪动了 
		ptree->nodes[index].data=' ';
		ptree->nodes[index].parent=-2;
	}
}

//递归删除一个结点的所有子结点,index为要删除的结点在数组中的下标
void deleteRecursion(PTree *ptree,int index){
	for(int i = 0 ; i < ptree->n ; i++){
		//找到了要删除的元素
		if(index == ptree->nodes[i].parent){
			deleteRecursion(ptree,i);
			//这里执行删除操作(将数组中的最后元素填充到此地,节省数组空间) 
			printf("递归删除的元素是%c\n",ptree->nodes[i].data);
			//数组中有元素,且删除的不是最后一个元素 
			if(ptree->n > 0 && i != ptree->n-1){
				ptree -> nodes[i].data = ptree->nodes[(ptree->n)-1].data;
				ptree ->nodes[i].parent =  ptree->nodes[(ptree->n)-1].parent;
			 	ptree->nodes[(ptree->n)-1].data = ' ';
			 	ptree->nodes[(ptree->n)-1].parent= -2;	
				(ptree->n)--;
				//i--必须要有,因为有可能最后一个元素也符合条件, i-- 是保证新移动过来的这个元素也参与了比较。 
				i--;
			}else{
				//删除的是最后一个元素 或者数组中已经没有元素可以挪动了
				ptree->nodes[i].data= ' ';
				ptree->nodes[i].parent=-2;
				//删除最后一个元素的时候,数组中元素的个数你也得减1啊 
				(ptree->n)--;
			}
		}
 	}
}
/*
	第二种方案:
		删除的时候,数组最后一个元素不再前移。而是在插入的时候,遍历数组,找到空闲位置然后进行插入。
	 	此种方式同样也不会造成数组存储空间的浪费。 而且不会出现孩子结点找不到父亲的情况。  
		这种方式比前一种就是遍历数组找元素的时候,可能会多遍历几个元素。--> 但是从正确性的角度来说优于第一种。 
		此种方式n就不能代表数组中有效元素个数。 
*/
void deletePNodeNew(PTree *ptree){
	printf("请输入您要删除的元素是:\n");
	ElemType ch;
	getchar();
	scanf("%c",&ch);
	int index = -1;
	for(int i=0;i< ptree->n;i++){
		if(ptree->nodes[i].data == ch){
			index = i;	
			break;
		}
	}
	//递归删除子元素 
	deleteRecursionNew(ptree,index);
	//删除完子元素之后,把该元素入数组 
	printf("最终删除的元素是%c\n",ptree->nodes[index].data);
	ptree -> nodes[index].data = ' ';
	ptree ->nodes[index].parent =-2;
}

void deleteRecursionNew(PTree *ptree,int index){
	for(int i = 0 ; i < ptree->n ; i++){
		//找到了要删除的元素
		if(index == ptree->nodes[i].parent){
			deleteRecursionNew(ptree,i);
			printf("递归删除的元素是%c\n",ptree->nodes[i].data);
			ptree -> nodes[i].data = ' ';
			ptree ->nodes[i].parent =-2;
		}
 	}
}

//配套的添加方法
void addPNodeNew(PTree *ptree){
	ElemType ch;
	int j;
	printf("您要添加的结点的值,以及该结点双亲位于数组的下标:\n");
	getchar();
	scanf("%c %d",&ch,&j);
	int flag = 0;
	for(int i = 0;i< ptree->n;i++){
		if(ptree->nodes[i].parent == -2){
	 		flag = 1;
			ptree->nodes[i].data = ch;
			ptree->nodes[i].parent = j;
			break;
		}
	}
	//如果在数组有效元素的范围内,没有找到空闲空间,判断一下数组空间是否已满 
	if(flag == 0){
		if(ptree->n>=MaxSize){
			printf("数组空间已满,已经放不下元素了\n");
			return;
		}
		ptree->nodes[ptree->n].data=ch;
		ptree->nodes[ptree->n].parent=j;
		ptree->n++;
		return;
	} 
	
} 
int main(int argc, char *argv[])
{
	PTree ptree;
	//初始化数组
	for(int i = 0;i< MaxSize ;i++){
		ptree.nodes[i].data=' ';
		ptree.nodes[i].parent=-2;
	}
	//双亲表示法存储树 
	ptree = InitPTree(ptree);
 	//查找某个树结点的父亲结点 
	findParent(ptree);
	//查找某个树结点的儿子结点 
	findChild(ptree);
	//如果删除的这个结点有子结点,那我们应该递归的删除完子结点,然后再删除该结点。 
	
	//删除之前打印一遍数组
	for(int i = 0;i<MaxSize	;i++){
		printf("%c %d\n",ptree.nodes[i].data,ptree.nodes[i].parent);
	}
	deletePNodeNew(&ptree);
	//删除之前打印一遍数组
	for(int i = 0;i<MaxSize	;i++){
		printf("%c %d\n",ptree.nodes[i].data,ptree.nodes[i].parent);
	}
	
	addPNodeNew(&ptree);
	addPNodeNew(&ptree);
	addPNodeNew(&ptree);
	addPNodeNew(&ptree);
	//添加完之后打印一遍数组
	for(int i = 0;i<MaxSize	;i++){
		printf("%c %d\n",ptree.nodes[i].data,ptree.nodes[i].parent);
	}
	
	return 0;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值