数据结构之树的存储(C语言版)

树的表达方式:

之前,我们介绍的所有的数据结构都是线性存储结构。本章,我们所介绍的树的结构是⼀种⾮线 性的存储结构。存储的是具有⼀对多的关系的数据元素的集合。

树的概念:

        图中可以看⻅⼀个使⽤树形结构存储的⼀个集合,这个集合就是{A,B,C.......}。对于数据A来 说,和数据B、C、D有关系。对于数据B来说,和E,F,G有关系。这就是⼀对多的关系。 我们将⼀对多的关系的集合中的数据元素按照图中的形式进⾏存储,整个存储形状在逻辑结果上 ⾯看,类似于实际⽣活中倒着的树,所以就将这种结构称之为树形结构。

 树的结点:

结点:使⽤树结构存储的每⼀个数据元素都被称为“结点”。例如图中的A就是⼀个结点。

根结点:有⼀个特殊的结点,这个结点没有前驱,我们将这种结点称之为根结点。

⽗结点(双亲结点)、⼦结点和兄弟结点:对于ABCD四个结点来说,A就是BCD的⽗结点,也称 之为双亲结点。⽽BCD都是A的⼦结点,也称之为孩⼦结点。对于BCD来说,因为他们都有同⼀个爹,所 以它们互相称之为兄弟结点。

叶⼦结点:如果⼀个结点没有任何⼦结点,那么此结点就称之为叶⼦结点。

结点的度:结点拥有的⼦树的个数,就称之为结点的度。

树的度:在各个结点当中,度的最⼤值。为树的度。

树的深度或者⾼度:结点的层次从根结点开始定义起,根为第⼀层,根的孩⼦为第⼆层。依次类 推

树的存储结构:

双亲表示法

顺序表表示形式

 

结点的定义:

结点中包含数据域和父结点的下标

typedef struct TreeNode {
	int data;//树中存放的真实的数据
	int parent;//父节点 -1代表没有父节点
}Node;

全局变量:

创建一个结构体数组从而用顺序表表示树

/*全局变量*/
Node* node[5];//父亲表示法的顺序表表示
int size= 0;;//当前元素的个数
int maxSize = 5;;//元素的总个数

void insert_root(int);//建立根节点
void insert_child(int,int);//插入元素
int find_parent(int);

创建根结点:

先创建一个根结点

再将key写入数据域

因为根结点无父结点,所以父亲域写入-1

之后将结构体数组的第一个1位置写入次结点

size再往后移动

/*
创建根节点
key 根节点的关键字
*/
void insert_root(int key) 
{
	Node* new_node = (Node*)malloc(sizeof(Node));
	new_node->data = key;
	new_node->parent = -1;
	node[size] = new_node;
	size++;
}

查找父结点的下标:

传入父结点的数值

通过对树的遍历循环来寻找

找到则返回下标

最终没找到返回下标-1

/*
找到父节点的下标 返回-1代表没找到
*/
int find_parent(int parent)
{
	for (int i = 0; i < size; i++) {
		if (parent == node[i]->data)
		{
			return i;
		}
	}
	return -1;
}

插入元素:

传入的参数是关键词key和父亲结点的值parent

先判断元素是否满了

如果没满,先调用查找函数来查找父结点的下标

判断父亲结点是否找到

如果找到了

就新建一个结点

其数据域写入key

之后让其父亲域指向其父亲结点

再将此结点放入结构体数组中

size往后移动一位

/*
插入元素
int key 关键字
int parent 父节点的值
*/
void insert_child(int key, int parent)
{
	if (size == maxSize)
	{
		//元素已满 要么提示 要么扩容
	}
	else 
	{
		//判断一下 是否有这个父节点
		int parent_index = find_parent(parent);
		if (parent_index == -1)
		{
			//没有该父节点
		}
		else
		{
			Node* new_node = (Node*)malloc(sizeof(Node));
			new_node->data = key;
			new_node->parent = parent_index;
			node[size] = new_node;
			size++;
		}
	}
}

 总代码:

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

typedef struct TreeNode {
	int data;//树中存放的真实的数据
	int parent;//父节点 -1代表没有父节点
}Node;

/*全局变量*/
Node* node[5];//父亲表示法的顺序表表示
int size= 0;;//当前元素的个数
int maxSize = 5;;//元素的总个数

void insert_root(int);//建立根节点
void insert_child(int,int);//插入元素
int find_parent(int);

/*
创建根节点
key 根节点的关键字
*/
void insert_root(int key) 
{
	Node* new_node = (Node*)malloc(sizeof(Node));
	new_node->data = key;
	new_node->parent = -1;
	node[size] = new_node;
	size++;
}

/*
插入元素
int key 关键字
int parent 父节点的值
*/
void insert_child(int key, int parent)
{
	if (size == maxSize)
	{
		//元素已满 要么提示 要么扩容
	}
	else 
	{
		//判断一下 是否有这个父节点
		int parent_index = find_parent(parent);
		if (parent_index == -1)
		{
			//没有该父节点
		}
		else
		{
			Node* new_node = (Node*)malloc(sizeof(Node));
			new_node->data = key;
			new_node->parent = parent_index;
			node[size] = new_node;
			size++;
		}
	}
}

/*
找到父节点的下标 返回-1代表没找到
*/
int find_parent(int parent)
{
	for (int i = 0; i < size; i++) {
		if (parent == node[i]->data)
		{
			return i;
		}
	}
	return -1;
}

最后,我们不难看出,顺序表结构下双亲表示法的树的功能无非初始化,查找和插入这三种。

优缺点说明

        由于根结点是没有双亲的,所以我们约定根结点的位置域设置为-1,这也就意味着,我们所有的 结点都存有它双亲的位置。这样的存储结构,我们可以根据结点的parent指针很容易找到它的双亲结 点,所⽤的时间复杂度为O(1),直到parent为-1时,表示找到了树结点的根。可如果我们要知道结点的 孩⼦是什么,对不起,请遍历整个结构才⾏。

        这真是麻烦,能不能改进⼀下呢?当然可以。我们增加⼀个结点最左边孩⼦的域,不妨叫它⻓子域,这样就可以很容易得到结点的孩⼦。如果没有孩子的结点,这个长子域就设置为-1。

        对于有0个或1个孩⼦结点来说,这样的结构是解决了要找结点孩子的问题了。甚⾄是有2个孩 ⼦,知道了长子是谁,另⼀个当然就是次子了。另外⼀个问题场景,我们很关注各兄弟之间的关系,双亲表示法⽆法体现这样的关系,那我们怎么办?嗯,可以增加⼀个右兄弟域来体现兄弟关系,也就是说, 每⼀个结点如果它存在右兄弟,则记录下右兄弟的下标。同样的,如果右兄弟不存在,则赋值为-1。

        但如果结点的孩子很多,超过了2个。我们⼜关注结点的双亲、⼜关注结点的孩子、还关注结点的兄弟,⽽且对时间遍历要求还⽐较高,那么我们还可以把此结构扩展为有双亲域、长子域、再有右兄弟域。存储结构的设计是⼀个⾮常灵活的过程。⼀个存储结构设计得是否合理,取决于基于该存储结构的运算是否适合、是否⽅便,时间复杂度好不好等。

孩子表示法

 

结点的定义:

结点包含数据域和孩子域

指针域用来指向当前结点的孩子

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

全局变量:

Node* node_array[100];//存储结点的数组
int size;//数组中元素的个数

初始化并建立根结点:

传入待写入根结点数据域的关键词key

之后将size赋值为0

将第一个结点的位置先申请一块空间

再将key写入数据域

再将孩子域指向NULL

/*初始化 并且建立根节点*/
void Init(int key)
{
	size = 0;
	//将新的结点添加到数组当中
	node_array[size] = (Node*)malloc(sizeof(Node));
	//给新节点赋值
	node_array[size]->data = key;
	node_array[size]->next = NULL;
	size++;
}

查找父结点:

传入关键字key

之后通过遍历查找

找到返回对应下标

没找到则返回-1

int find_parent(int parent)
{
	for (int i = 0; i < size; i++)
	{
		if (node_array[i]->data == parent) {
			return i;
		}
	}
	return -1;
}

插入结点:

传入父结点的值parent和关键字key

之后再当前结构体size位置申请一块空间

之后给新结点赋值

顺便将其孩子结点指向空

之后是调用查找函数查找父结点的下标

先判断是否找到

如果找到则

创建一个结点

将数据域写入key,孩子域指向父亲域的孩子域(即头插法)

再让其父亲的孩子域指向当前结点

/*
int parent 父节点的值
int key  孩子结点的值
*/
void creat_tree(int parent, int key) 
{
	//先将孩子结点添加到数组当中
	node_array[size] = (Node*)malloc(sizeof(Node));
	//给新节点赋值
	node_array[size]->data = key;
	node_array[size]->next = NULL;
	size++;
	//找到父节点
	int index = find_parent(parent);
	if (index == -1)
	{

	}
	else 
	{
		Node* new_node = (Node*)malloc(sizeof(Node));
		new_node->data = key;
		new_node->next = node_array[index]->next;
		node_array[index]->next = new_node;
	}

}

总代码:

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

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

Node* node_array[100];//存储结点的数组
int size;//数组中元素的个数

void Init(int);//初始化操作
void creat_tree(int, int);//构建树
int find_parent(int);//找到父节点

int main()
{
	Init(1);
	creat_tree(1, 2);
	creat_tree(1, 3);
	creat_tree(1, 4);
	creat_tree(2, 5);
	creat_tree(2, 6);
	creat_tree(3, 7);
	for (int i = 0; i < size; i++)
	{
		printf("父节点为%d", node_array[i]->data);
		Node* temp = node_array[i]->next;
		while (temp != NULL)
		{
			printf("孩子结点为%d", temp->data);
			temp = temp->next;
		}
		printf("\n");
	}
}
/*初始化 并且建立根节点*/
void Init(int key)
{
	size = 0;
	//将新的结点添加到数组当中
	node_array[size] = (Node*)malloc(sizeof(Node));
	//给新节点赋值
	node_array[size]->data = key;
	node_array[size]->next = NULL;
	size++;
}

/*
int parent 父节点的值
int key  孩子结点的值
*/
void creat_tree(int parent, int key) 
{
	//先将孩子结点添加到数组当中
	node_array[size] = (Node*)malloc(sizeof(Node));
	//给新节点赋值
	node_array[size]->data = key;
	node_array[size]->next = NULL;
	size++;
	//找到父节点
	int index = find_parent(parent);
	if (index == -1)
	{

	}
	else 
	{
		Node* new_node = (Node*)malloc(sizeof(Node));
		new_node->data = key;
		new_node->next = node_array[index]->next;
		node_array[index]->next = new_node;
	}

}

int find_parent(int parent)
{
	for (int i = 0; i < size; i++)
	{
		if (node_array[i]->data == parent) {
			return i;
		}
	}
	return -1;
}

 孩子兄弟表示法

 

二叉链表的定义:

结点中包含数据域data,第一个孩子的指针域child,兄弟的指针域sibling

之后初始化一个指针,指向根结点的指针,同时也标记了整棵树

再初始化一个临时指针t

//二叉链表的结点结构
typedef struct ChildSibling{
	int data;//数据	
	struct ChildSibling* child;// 第一个孩子指针域
	struct ChildSibling* sibling;//兄弟指针域
}Node; 
Node* root;//指向根节点的指针,同时也标记整棵树
Node* t;//临时指针 

 初始化建立根结点:

将root指针指向的结点申请一块空间

将key写入数据域

之后将孩子指针域指向空

兄弟指针域指向空

//初始化,建立根节点
void Init(int key)
{
	root=(Node*)malloc(sizeof(Node));
	root->data =key;
	root->child =NULL;
	root->sibling =NULL;	
 } 

查找父结点:

传入指向树的指针r和父结点的数据parent

先判断r的属于域是否为parent

如果是则返回r

再判断孩子指针域是否为空

不为空则创建指针x,让他为以child为根结点的子树的指针

再次调用此查找方法(递归)

判断如果x不为空,且x的数据域为parent,则返回x

之后再判断r的兄弟指针域是是否为空

不为空则创建指针x,标记以r的兄弟sibling为根结点的子树

再次调用此查找方法(递归)

判断如果x不为空,且x的数据域为parent,则返回x

最后如果还是没找到则返回空

//在以r为根的树中,查找数据为parent的结点
//递归 
Node* getNode(Node* r,int parent)
{
	if(r->data ==parent)//1
	{
		return r;
	}
    if(r->child !=NULL)//2
    {
        Node* x=getNode(r->child,parent);//调1 
        if(x!=NULL&&x->data ==parent)
        {
        	return x;
		}

	
	}
	if(r->sibling !=NULL)//3 
	{
	
        Node* x=getNode(r->sibling ,parent);//调2 
        if(x!=NULL&&x->data ==parent)
        {
        	return x;
		}	
	}
	return NULL;
	
 } 

结点插入函数:

key是插入的数据,parent是key的父亲结点的数据

先将临时指针t接受查找函数getNode返回的地址

再判断地址是否为空

若不为空则创建指针p,指向申请的一块结点

并将结点的数据域写入key

之后判断t的孩子指针域是否为空(此时t为待插入结点的父亲结点)

不为空则说明key不是parent的第一个孩子

则将t的孩子指针赋给t(此时t为第一个孩子的结点)

之后让p结点的兄弟域指针指向t的兄弟域指针所指向的

再让t的兄弟域指针指向p

再将p的孩子域指针指向空(头插法实现)

如果为空则说明是第一个孩子

那么t的孩子域指针指向p

p的兄弟域指针和孩子域指针都指向空

//插入 :key是插入的数据,parent是key的父亲结点的数据 
void insert(int key,int parent)
{
	t=getNode(root,parent); 
	if(t!=NULL)
	{
		Node* p=(Node*)malloc(sizeof(Node));
		p->data=key;
	
		if(t->child !=NULL)//key 不是parent的第一个孩子 
	   {
	   	    t=t->child ;
	   	    p->sibling =t->sibling;
	   	    t->sibling =p;
	   	    p->child =NULL;
	   }
		else{//key 是parent的第一个孩
			t->child =p;
			p->child =NULL;
			p->sibling=NULL;
			
		} 
	 } 
	
	else{
		//
	}
	
}

总代码:

#include<stdio.h>
#include<stdlib.h>
//二叉链表的结点结构
typedef struct ChildSibling{
	int data;//数据	
	struct ChildSibling* child;// 第一个孩子指针域
	struct ChildSibling* sibling;//兄弟指针域
}Node; 
Node* root;//指向根节点的指针,同时也标记整棵树
Node* t;//临时指针 

//初始化,建立根节点
void Init(int key)
{
	root=(Node*)malloc(sizeof(Node));
	root->data =key;
	root->child =NULL;
	root->sibling =NULL;	
 } 
//在以r为根的树中,查找数据为parent的结点
//递归 
Node* getNode(Node* r,int parent)
{
	if(r->data ==parent)//1
	{
		return r;
	}
    if(r->child !=NULL)//2
    {
        Node* x=getNode(r->child,parent);//调1 
        if(x!=NULL&&x->data ==parent)
        {
        	return x;
		}

	
	}
	if(r->sibling !=NULL)//3 
	{
	
        Node* x=getNode(r->sibling ,parent);//调2 
        if(x!=NULL&&x->data ==parent)
        {
        	return x;
		}	
	}
	return NULL;
	
 } 
 
 
//插入 :key是插入的数据,parent是key的父亲结点的数据 
void insert(int key,int parent)
{
	t=getNode(root,parent); 
	if(t!=NULL)
	{
		Node* p=(Node*)malloc(sizeof(Node));
		p->data=key;
	
		if(t->child !=NULL)//key 不是parent的第一个孩子 
	   {
	   	    t=t->child ;
	   	    p->sibling =t->sibling;
	   	    t->sibling =p;
	   	    p->child =NULL;
	   }
		else{//key 是parent的第一个孩
			t->child =p;
			p->child =NULL;
			p->sibling=NULL;
			
		} 
	 } 
	
	else{
		//
	}
	
}
//主函数自行写出 

        这种表示法,给查找某个结点的某个孩⼦带来了⽅便,只需要通过firstchild找到此结点的⻓⼦, 然后再通过⻓⼦结点的rightsib找到它的⼆弟,接着⼀直下去,直到找到具体的孩⼦。当然,如果想找某 个结点的双亲,这个表示法也是有做陷的,那怎么办呢?

         对,如果真的有必要,完全可以再增加⼀个parent指针域来解决快速查找双亲的问题,这⾥就不 再细谈了。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值