图解树的3种表示方法(含完整C代码)

前一节介绍了树的基本概念,这一节咱们就来看看树的表示方法。我们应该用什么样的存储方式来存储一棵树呢?我们目前学习了数据的顺序存储和链式存储,其实树就是以这两种基本存储方法构建起来的。

目录

🌟1.双亲表示法

💓2.孩子表示法

🌵3.孩子兄弟表示法

🌟1.双亲表示法

双亲表示法是用顺序表,也就是数组对树进行表示的。

每一个结点包含存储的数据与其父节点的数组下标,多个结点用一组连续的地址空间,即数组来存储。

我们来看这一棵树:

它的存储方式为: 

 详细说明:

  1. 因为根节点没有父节点,将其父节点数组下标设置为-1,根节点存储在顺序表的第1个位置;
  2. 数据元素2、3、4的父节点为1,父节点数组下标为1,分别存储在顺序表的2、3、4个位置;
  3. 数据元素5、6的父节点为2,父节点数组下标为2,分别存储在顺序表的第5、6个位置;
  4. 数据元素7的父节点为3,父节点数组下标为3,存储在顺序表的第7个位置

1.显然就可以写出,存储结点的代码:

typedef struct TreeNode
{
	int data;//树中存放的数据
	int parent;//父节点的位置
}Node;

 2.初始化这棵树,也就是建立根节点:

void init(int n)
{
	size = 0;
	maxsize = 10;
	/*创建根节点*/
	Node* new_node = (Node*)malloc(sizeof(Node));
	new_node->data = n;//根结点存储元素
	new_node->parent = -1;//根结点的没有父节点,赋为-1
	node[size++] = new_node;//将根结点添加到数组中,size+1
}

3.通过以上的详细说明可以写出建立树,也就是插入树的结点:

  1. 首先要明确在哪里插入,也就是找到插入结点的父节点
  2. 然后在顺序表中依次添加内容
void insert(int n,int p)
{
	if (size == maxsize)
	{
		printf("已存满,添加失败\n");
	}
	else
	{
		//先要找到有没有指定的父亲结点
		int i = find(p);
		if (i = -1)
		{
			Node* new_node = (Node*)malloc(sizeof(Node));
			new_node->data = n;
			new_node->parent = p;//它的父亲结点元素为p
			node[size++] = new_node;//在数组中依次向后添加
		}
		else
		{
			printf("没有此父节点,添加失败\n");
		}
	}
}

优缺点:

优点:若要找到某个结点的父节点,可以很容易找到,因为顺序表已经存好了每个节点的父节点位置;

缺点:但是要找某个结点的孩子节点,那就比较麻烦了,需要去遍历整个结构。

完整代码:

#include<stdio.h>
#include<stdlib.h>
typedef struct TreeNode
{
	int data;//树中存放的数据
	int parent;//父节点的位置
}Node;

Node* node[10];//顺序表表示
int size;//当前元素个数
int maxsize;//元素总个数

void init(int);//初始化并建立根节点
void insert(int, int);//建立孩子结点在指定父亲结点下
int find(int);//查找该结点的父节点

void init(int n)
{
	size = 0;
	maxsize = 10;
	/*创建根节点*/
	Node* new_node = (Node*)malloc(sizeof(Node));
	new_node->data = n;//根结点存储元素
	new_node->parent = -1;//根结点的没有父节点,赋为-1
	node[size++] = new_node;//将根结点添加到数组中,size+1
}
void insert(int n,int p)
{
	if (size == maxsize)
	{
		printf("已存满,添加失败\n");
	}
	else
	{
		//先要找到有没有指定的父亲结点
		int i = find(p);
		if (i = -1)
		{
			Node* new_node = (Node*)malloc(sizeof(Node));
			new_node->data = n;
			new_node->parent = p;//它的父亲结点元素为p
			node[size++] = new_node;//在数组中依次向后添加
		}
		else
		{
			printf("没有此父节点,添加失败\n");
		}
	}
}
int find(int p)
{
	//如果该元素p等于数组中的某元素,则说明找到了,返回其在数组中的位置
	for (int i = 0; i < size; i++)
	{
		if (p == node[i]->data)
			return i;
	}
	//否则,返回-1
	return -1;
}
int main()
{
	init(1);
	insert(2, 1);
	insert(3, 1);
	insert(4, 1);
	insert(5, 2);
	insert(6, 2);
	insert(7, 3);
	for (int i = 0; i < size; i++)
	{
		printf("父节点为%d ", node[i]->parent);
		printf("该结点为%d\n", node[i]->data);

	}
	return 0;
}

 输出结果:

 

💓2.孩子表示法

刚刚的双亲表示法,因为没有存储孩子结点的位置,所以找孩子结点比较麻烦,于是前辈们又想出来了另外一种方法,叫孩子表示法,这种方法融合了顺序存储与链式存储结构,可以很快的找到孩子们。

我们先来看看这棵树:

 它的存储方式:

 详细说明:

添加一个数据(插入一个结点)

  1. 既要在数组中依次添加新的数据
  2. 也要在其父节点后面用头插法插入

(头插法是不是很熟悉,如果不熟悉,可以参考这篇文章)

单链表icon-default.png?t=M276https://blog.csdn.net/weixin_54186646/article/details/123312019?spm=1001.2014.3001.5501

  1. 添加第一个数据1,作为根节点,加入到数组中第1个位置,它没有父节点;
  2. 添加第二个数据2,其父节点为1,加入2到数组的第2个位置,在其父节点1后插入第1个孩子;
  3. 添加第三个数据3,其父节点为1,加入3到数组的第3个位置,在其父节点1后插入第2个孩子;
  4. 添加第四个数据4,其父节点为1,加入4到数组的第4个位置,在其父节点1后插入第3个孩子;
  5. 添加第五个数据5,其父节点为2,加入5到数组的第5个位置,在其父节点2后插入第1个孩子;
  6. 添加第六个数据6,其父节点为2,加入6到数组的第6个位置,在其父节点2后插入第2个孩子;
  7. 添加第七个数据7,其父节点为3,加入7到数组的第7个位置,在其父节点3后插入第1个孩子。

在插入的过程中,我们可以看出来:

  • 插入孩子结点采用头插法,即先插入的元素到了后面,后插入的元素在前面
  • 数组的类型与孩子结点的类型相同,也是一个结构体,包含一个数据域,一个指针域

1.显然可以写出,存储结构代码

typedef struct LinkList 
{
	int data;//存放数据
	struct LinkList* next;
}Node;

2.初始化根节点

void init(int n)
{
	size = 0;
	maxsize = 10;
	//其实生成一个new_node,然后再赋值给数组也行
	node[size] = (Node*)malloc(sizeof(Node));
	node[size]->data = n;
	node[size]->next = NULL;//根节点暂时没有孩子,next为空
	size++;
}

3.先存入数组,然后插入到父节点后面

/*将数据n插入到结点p下面*/
//即将n作为p的孩子结点
void insert(int n, int p)
{
	if (size == maxsize)
	{
		printf("已存满,插入失败\n");
	}
	else
	{
		int i = find(p);
		if (i != -1)
		{
			//先将孩子结点放入到数组中
			node[size] = (Node*)malloc(sizeof(Node));
			node[size]->data = n;
			node[size]->next = NULL;//此结点暂时没有孩子结点
			size++;

			//再将结点插入到父节点next中
			Node* new_node = (Node*)malloc(sizeof(Node));
			new_node->data = n;
			new_node->next = node[i]->next;//类似于头插法
			node[i]->next = new_node;//将新插入的孩子结点放在父节点后一个位置,即父节点原有孩子结点的前面
		}
		else
			printf("没有找到此父节点,插入失败\n");
	}
}

完整代码

#include<stdio.h>
#include<stdlib.h>

typedef struct LinkList 
{
	int data;//存放数据
	struct LinkList* next;
}Node;

Node* node[10];//存储结点的数组
int size;//数组中元素的个数
int maxsize;

void init(int);//初始化操作
void insert(int, int);//构建树
int find(int);//找到父节点

void init(int n)
{
	size = 0;
	maxsize = 10;
	//其实生成一个new_node,然后再赋值给数组也行
	node[size] = (Node*)malloc(sizeof(Node));
	node[size]->data = n;
	node[size]->next = NULL;//根节点暂时没有孩子,next为空
	size++;
}
int find(int p)
{
	for (int i = 0; i < size; i++)
	{
		if (p == node[i]->data)
			return i;
	}
	return -1;
}

/*将数据n插入到结点p下面*/
//即将n作为p的孩子结点
void insert(int n, int p)
{
	if (size == maxsize)
	{
		printf("已存满,插入失败\n");
	}
	else
	{
		int i = find(p);
		if (i != -1)
		{
			//先将孩子结点放入到数组中
			node[size] = (Node*)malloc(sizeof(Node));
			node[size]->data = n;
			node[size]->next = NULL;//此结点暂时没有孩子结点
			size++;

			//再将结点插入到父节点next中
			Node* new_node = (Node*)malloc(sizeof(Node));
			new_node->data = n;
			new_node->next = node[i]->next;//类似于头插法
			node[i]->next = new_node;//将新插入的孩子结点放在父节点后一个位置,即父节点原有孩子结点的前面
		}
		else
			printf("没有找到此父节点,插入失败\n");
	}
}
int main()
{
	init(1);
	insert(2, 1);
	insert(3, 1);
	insert(4, 1);
	insert(5, 2);
	insert(6, 2);
	insert(7, 3);
	for (int i = 0; i < size; i++)
	{
		printf("父节点为%d ", node[i]->data);
		Node* temp = node[i]->next;
		while (temp != NULL)
		{
			printf("孩子结点为%d ", temp->data);
			temp = temp->next;
		}
		printf("\n");
	}
}

输出结果:

 

🌵3.孩子兄弟表示法

这种表示方式也可以很快找到孩子结点,不过有点绕,要耐心理解嗷~

我们先来看看这棵树,这也是它的存储方式:

 每个结点包含了数据域、孩子指针域、兄弟指针域

注意:这里的2、3、4互为兄弟,也都是1的孩子;5、6互为兄弟,也都是2的孩子。

详细说明:

这种插入方式:

  1. 同样也要先找到在谁的后面插入,即找到父节点
  2. 然后再看看父节点的孩子指针空不空
  3. 空的话就插入到父节点的孩子指针域里,如果这个位置有结点的话(孩子指针域不空),就要插入到这个孩子结点的兄弟指针域里,插入方式还是头插法。
  1. 添加根节点1,其孩子、兄弟指针域为空;
  2. 添加结点2,其父节点为1,父节点的孩子指针域为空,就插入到此位置(将父节点1的孩子指针指向2),2的孩子、兄弟指针为空;
  3. 添加结点3,其父节点为1,父节点的孩子指针域不空,就插入到其孩子结点的兄弟指针域里(将孩子结点2的兄弟指针指向3),3的孩子、兄弟指针为空;
  4. 添加结点4,其父节点为1,父节点的孩子指针域不空,就插入到其孩子结点的兄弟指针域里(将孩子结点2的兄弟指针指向4),4的孩子指针为空、兄弟指针指向原来的3(这里的图就有一点问题,4应该在3的上面)
  5. 添加结点5,其父节点为2,父节点的孩子指针域为空,就插入到此位置(将父节点2的孩子指针指向5),5的孩子、兄弟指针为空;
  6. 添加结点6,其父节点为2,父节点的孩子指针域不空,就插入到其孩子结点的兄弟指针域里的(将孩子结点5的兄弟指针指向6),6的孩子、兄弟指针为空;
  7. 添加结点7,其父节点为3,父节点的孩子指针域为空,就插入到此位置(将父节点3的孩子指针指向7),7的孩子、兄弟指针为空;

1.显然,它的存储方式:

typedef struct ChildSibling {
	int data;//当前结点的数据
	struct ChildSibling* child;//指向孩子的指针
	struct ChildSibling* sibling;//指向兄弟的指针
}Node;

2.初始化根节点:

void Init(int key)
{
	root = (Node*)malloc(sizeof(Node));
	root->data = key;
	root->child = NULL;
	root->sibling = NULL;
}

3.先插入到孩子指针域里,满了就插入到孩子结点的兄弟指针域那里:

/*
int key  待插入的数据
int parent 数据的父节点
*/
void insert(int key, int parent)
{
	//定位父节点
	temp = get_node(root, parent);
	if (temp == NULL)
	{
		printf("没有此结点,插入失败\n");
	}
	else
	{	//左孩子不为空,就插入到左孩子的兄弟结点当中,类似头插法(兄弟结点可能已经有结点了)
		if (temp->child != NULL)
		{
			temp = temp->child;
			Node* node = (Node*)malloc(sizeof(Node));
			node->data = key;
			node->child = NULL;
			node->sibling = temp->sibling;
			temp->sibling = node;
		}
		//左孩子为空,就插入到左孩子的位置
		else
		{
			Node* node = (Node*)malloc(sizeof(Node));
			node->child = NULL;
			node->sibling = NULL;
			node->data = key;
			temp->child = node;
		}
	}
}

注意:前面两种方法因为有数组,所以可以通过访问数组下标的方式找到父节点,而孩子兄弟表示法没有用数组,那该怎么找父节点呢?

只有一个一个找了,这里用递归来实现(后面的前、中、后序遍历树也要用到递归的方法嗷~)

Node* get_node(Node* node, int parent)
{
	if (node->data == parent)
	{
		return node;
	}
	if (node->child != NULL)
	{
		get_node(node->child, parent);
	}
	if (node->sibling != NULL)
	{
		get_node(node->sibling, parent);
	}
	return NULL;
}

那这里就不给出完整代码了,弄懂了原理,自己动手试试吧!

那今天就先到这里了,下一篇树的遍历!

  • 12
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

~在下小吴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值