树的存储方式

在树中,每个数据都有其相应的父亲节点和多个孩子节点
所以当我们要储存数据时,只要我们知道其的父亲节点,我们就可以进行储存
或者我们知道这个数据的孩子有哪些,我们同样可以根据这些数据建立一个树
所以在这里就衍生出了三种储存方式,父亲表示法,孩子表示法,孩子兄弟表示法

首先第一种父亲表示法(结构体数组)链表也是类似
既然我们要知道一个数据的父亲是谁,那我们就需要一个数据来记录这个其父亲数据的节点或者数据本身
在这里我们选择用父亲节点的序号来表示

#include<stdio.h>
#include<stdlib.h>
#define  maxx  20
struct tree {
	char tdate;
	int fa_i;//父亲的序号
}tree[maxx];
int size;

maxx表示树中节点的最大个数
size表示现在存储的数据个数,也可以充当数组下标
结构体中的数据一个是数据,另一个是数据的父亲节点

接下来就是树的一些操作
查找,初始化,建树(添加数据)
查找其实很简单就是去遍历数组,然后返回数据的下表

int tfind(char fx) {
	for (int i = 0; i <= size; i++) {
		if (tree[i].tdate == fx) {

			return i;
		}
	}
}

初始化,因为树的根节点操作有些不一样,不存在其的父亲节点,所以我们需要一个单独的函数进行

void inittree(char k) {
	tree[size].tdate = k;
	tree[size].fa_i = -1;
	size++;
}

建树即添加数据,因为结构体数组中要储存其父亲节点的下表,所以我们需要通过查找函数去找到其的父亲节点

void addtree(char x, char fx) {
	tree[size].tdate = x;
	int fa = tfind(fx);
	tree[size].fa_i = fa;
	size++;
}

下面是主函数的代码,可以进行验证


int main() {
	int m;//
	scanf_s("%d", &m);//
	getchar();//
	char k, x, fx;
	scanf_s("%c", &k, 1);
	inittree(k);//
	for (int i = 2; i <= m; i++) {
		getchar();
		scanf_s("%1c %1c", &x, 1, &fx, 1);
		addtree(x, fx);
	}
	char a;
	getchar();
	scanf_s("%c", &a, 1);
	int y = tfind(a);

	int fa = tree[y].fa_i;
	printf("%d\n", fa);
	if (fa == -1) {
		printf("%c是根节点,无父亲", a);
		return 0;
	}
	printf("%c的父亲节点是%c", a, tree[fa].tdate);
	return 0;

}
/*
13
A
B A
C A
D A
E B
F B
G C
H D
I D
J D
K E
L E
M H

*/

getchar,是字符读入防止数组中储存的数据是类似换行这类的

孩子储存法
这里的结构体里面要储存的数据就不是两个基础数据类型了,因为一个数据的父亲只有一个,但它的孩子个数就不固定了
如果孩子还是用数组来储存,那就会导致一个问题,内存浪费,因为我们要是在结构图中定义数组,那我们就要按最大的定义
所以这里选择用链表而且是不带头节点的链表

#include<stdio.h>
#include<stdlib.h>
#define maxx 20
typedef struct tnode {
	int ti;
	struct tnode* next;
}Tnode;
struct Tree {
	char date;
	Tnode* ch;
}tree[maxx];
int size;

第一个结果体用来储存孩子的下表(这里也可以直接储存孩子的数据,但因为我们第二个结构体用的数组,所以我们知道下标和数据没什么区别)
第二个就是有关树的结构体
size和maxx作用与上面一样

同样的初始化,查找,建树

初始化

void inittree(char k) {
	tree[size].date = k;
	tree[size].ch = NULL;
	size++;
}

查找

int find(char fx) {
	for (int i = 0; i <= size; i++) {
		if (tree[i].date == fx) {

			return i;
		}
	}
}

建树
这个就需要一点变化了,在父亲表示法中是把父亲的下标直接存储进结构体,但这里就需要找到父亲的下标后
在父亲的那个结构体中进行操作
在这里我们使用头插法,如果限制顺序那我们就用尾插

void addtree(char x, char fx) {
	int i = find(fx);
	Tnode* s = (Tnode*)malloc(sizeof(Tnode));
	if (s == NULL) {
		printf("内存分配失败");
		return;
	}
	s->ti = size;
	s->next = tree[i].ch;
	tree[i].ch = s;
	tree[size].date = x;
	tree[size].ch = NULL;
	size++;
}

最后主函数

int main() {
	int m;//
	scanf_s("%d", &m);//
	getchar();//
	char k, x, fx;
	scanf_s("%c", &k, 1);
	inittree(k);//
	for (int i = 2; i <= m; i++) {
		getchar();
		scanf_s("%1c %1c", &x, 1, &fx, 1);
		addtree(x, fx);
	}
	char a;
	getchar();
	scanf_s("%c", &a, 1);
	int y = find(a);
	Tnode* q = tree[y].ch;
	if (q == NULL) {
		printf("没有孩子节点");
	}
	else {
		while (q) {
			printf("%c的孩子有%c\n", a, tree[q->ti].date);
			q = q->next;

		}
	}
	return 0;
}

兄弟孩子表示法
兄弟孩子表示法其实就是另类的孩子表示法
在孩子表示法中我们能知道一个数据的孩子有谁,但是我们不好去通过一个数据找他的兄弟,我们要先找到这个数据的父亲
再通过父亲找到这个数据的兄弟
所以在这里我们引入二叉链表
一个指针指向孩子,一个指针指向其的兄弟,再来一个数据域储存数据

#include<stdio.h>
#include<stdlib.h>
typedef struct Tnode {
	char date;
	struct Tnode* ch;
	struct Tnode* bro;
}Tnode,*Linktree;

在这棵树中他的一些操作与上面是不同的
查找
这里的查找是基于递归的
在普通链表中我们定义一个指针然后让这个指针不断指向下一个数据节点
但我们这里有两个指针域就不适合这里
要使用递归有个主要的思想就是这个问题能拆分成多个类似的小问题,这里就符合这种思想、
先判断这个树是不是空树
先看这个节点的数据看是否是我们找的数据,不是那就去这个节点的孩子节点找,再不是就去这个节点的兄弟节点找
如果找不到那就返回空

Tnode* find(Linktree tree, char fx) {
	if (tree->date == fx) {
		return tree;
	}
	if (tree->ch != NULL) {
		Tnode* ans = find(tree->ch, fx);
		if (ans != NULL && ans->date == fx) {
			return ans;
		}
	}
	if (tree->bro != NULL) {
		Tnode* ans = find(tree->bro, fx);
		if (ans != NULL && ans->date == fx) {
			return ans;
		}
	}
	return NULL;
}

初始化,刚创建一个节点,那他的孩子节点必定为空,因为这是根节点所以其的兄弟节点也为空

Linktree inittree(char k) {
	Tnode* s = (Tnode*)malloc(sizeof(Tnode));
	if (s == NULL) {
		printf("内存分配失败");
		return s;
	}
	s->date = k;
	s->ch = NULL;
	s->bro = NULL;
	return s;
}

建树
在这里我们需要先建立一个新节点储存数据,这个节点的孩子节点必为空,而这个节点的兄弟节点就要去找其的父亲节点,
让它的兄弟节点等于其父亲节点的孩子节点,让它成为其父亲的孩子节点

void addtree(Linktree tree, char x, char fx) {
	Tnode* s = (Tnode*)malloc(sizeof(Tnode));
	if (s == NULL) {
		printf("内存分配失败");
		return;
	}
	s->date = x;
	s->ch = NULL;
	Tnode* q = find(tree, fx);
	s->bro = q->ch;
	q->ch = s;
	return;
}

在这里有人会说,这样找出来的兄弟其实并不全,只能表示出来在它前面输入数据中的兄弟,所以这里可以在结构体中在定义一个指针域
使这个兄弟链表变成双向链表
主函数

int main() {
	int m;//
	scanf_s("%d", &m);//
	getchar();//
	char k, x, fx;
	scanf_s("%c", &k, 1);
	Linktree tree = inittree(k);
	for (int i = 2; i <= m; i++) {
		getchar();
		scanf_s("%1c %1c", &x, 1, &fx, 1);
		addtree(tree,x, fx);
	}
	char a;
	getchar();
	scanf_s("%c", &a, 1);
	Tnode* q = find(tree, a);
	q = q->ch;
	if (q== NULL) {
		printf("%c没有孩子\n", a);
	}
	else {
		
		while (q) {
			
			printf("%c的孩子是%c\n", a, q->date);
			q = q->bro;
		}
	}
	return 0;
}
posted @ 2026-01-08 18:17  爱偷懒的我  阅读(10)  评论(0)    收藏  举报