数据结构-C语言描述(树)

本文介绍了树状结构的概念,如树的度数、结点分类,以及如何通过链表实现树结点和树边的创建。详细讲解了如何在树结构中添加子结点,创建树的实例,并演示了深度优先遍历的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述

我们先回顾一下常见的物理结构,包括:集合结构、线性结构、树状结构和图结构。而从树状结构开始数据元素之间的关系就不是简单的一对一了,变成了一对多或者多对多,简而言之就是元素之间关系变得更加复杂了。而我们今天要学习的就是典型的一种一对多的物理结构——树状结构。

首先我们通过一棵生活中常见的树来建立一个树的整体概念:

我们来观察一下, 从树根开始慢慢往上,发现逐渐由新的分支出现,而且分支上还可能会有分支。此时我们思考一下生活中有怎样的数据与树的结构相同?

例如图书馆中书籍的分类:

我们先想一下我们如何在图书馆中找到我们需要的那本书?

  • 首先要知道它在几楼
  • 然后明确它在某楼的几号阅览室
  • 在知道它在某号阅览室的几排书架
  • 然后是该书架的第几排
  • 这排的第几本

我们将这个步骤与树状结构联系起来:一个分叉点会有多个分支,相对应的,一楼会有多个阅览室、一个阅览室会有多排书架……

由此可见,树状结构的物理结构在生活中的应用还是比较广泛的。

1.树的相关概念

树的度数

 看图我们发现,图中的这棵树很规律的分为了四层,我们称这棵树的度数为4

结点

我们如果通过关系将结点分类的话,可分为两类(parentNode和childNode)。

以这张图为例,下结点是有直接联系的上层结点的子结点(A、B、C都是R的子结点;D、E都是A的子结点)。又有,同一结点的子结点互为兄弟结点(G、H、I互为兄弟结点)。 

如果根据度数分类的话,可分为三类根结点root(该图中R)、叶子结点leafNode(无子结点的结点D、E、I)、普通结点(A、B、C、F)。

2.链表结点的创建

首先先将头文件和宏文件写好:

#include <stdio.h>
#include <stdlib.h>
#define eletype char

 这里我们使用链表来连接相关结点,所以我们定义一个结构体ListNode:

struct TreeNode;  //因为在定义ListNode的时候要用到TreeNode,所以在这先定义一下
typedef struct ListNode {
    struct TreeNode* data;  //ListNode是一个记录TreeNode的链表,*data用来记录树结点的数据
    struct ListNode *next;  //*next用来指向下一个记录TreeNode链表
} ListNode;

3.树结点的创建

树结点应该包括数据域data和该结点下的子结点链表*childNode

typedef struct TreeNode {
    eletype data;            //数据域data用来记录树结点中的数据
    ListNode *childrenHead;  //*childHead用来记录该结点下的子结点链表
} TreeNode;

4.添加树边

因为树结点间的特殊关系,我们通常要将两个树结点(一般是结点与它的子结点)之间建立联系(称之为添加树边)。而要完成这一步我们首先要将参数传入,包括parentNodechildNode

void AddTreeChild (TreeNode* parent,TreeNode* child) {
    ListNode *childNode=(ListNode*)malloc(sizeof(ListNode));
    childNode->data=child;     //新定义一个结点childNode来接收child
    childNode->next=NULL;      //然后将其*next置空
    if(parent->childrenHead==NULL){      //先判断母结点的孩子结点链表是否为空
        parent->childrenHead=childNode;  //若为空,则直接将childNode作为子链表头指针
    } else {
        childNode->next=parent->childrenHead; //注意这里用的都是头插法
        parent->childrenHead=childNode;
    }
}

5.树结构体的定义

然后我们需要整棵树的框架(即用类型为TreeNode的一个数组nodes来记下每个树结点):

typedef struct Tree {
    TreeNode *nodes;   //用来储存树结点
    TreeNode *root;
} Tree;

6.树的创建

我们先要清楚创建树时,需要初始化的仅有在Tree中定义的*node*root,我们之前定义的结构是会包括在*node里的:

void TreeCreat (Tree* t,int size) {
    t->nodes=(TreeNode*)malloc(sizeof(TreeNode)*size);   //先让*node指向空
    t->root=NULL;   //*root也指向空
}

7.树的销毁

void TreeDestroy (Tree* t) {
    free(t->nodes);  //将*node树的空间释放掉
    t->nodes=NULL;  //然后将两个指针置空
    t->root;
}

8.获取树结点

TreeNode* GetTreeNode (Tree* t,int ID) {
    return &t->nodes[ID];
}

9.给树结点添加边

这一步只需要将要添加的两结点传入,并且调用AddTreeChild函数添加:、

void TreeAddChild (Tree* t,int parentId,int childId) {
    TreeNode* parentNode=GetTreeNode(t,parentId);
    TreeNode* childNode=GetTreeNode(t,childId);
    AddTreeChild(parentNode,childNode);
}

10.设置根结点

void TreeAssignRoot (Tree* t,int Id) {
    t->root=GetTreeNode(t,Id);    //调用TreeGetNode函数找到目标结点
}                                 //然后将该结点赋给*root

11.设置结点值

先要将结点值传入,然后找到要赋值的结点:

void TreeAssignData (Tree* t,int Id,eletype value) {
    GetTreeNode(t,Id)->data=value;
}

12.深度优先遍历(简化篇)

深度优先遍历是一种常见的遍历方法,举个例子,

 若以根结点R为起点,进行深度优先遍历,其路径为R->A->D。即在搜索的过程中,查找下一个节点的原则是往树的更深处查找。如果想深入了解深度优先遍历的过程可以参考离散数学图的遍历

void TreePrint (Tree* t,TreeNode* node) {
    if(node=NULL){
        node=t->root;
    } 
    printf("%c",node->data);
    ListNode* tmp=node->childrenHead;
    while(tmp){
        TreePrint(t,tmp->data);
        tmp=tmp->next;
    }
    
}

完整源码

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

#define eletype char

struct TreeNode;

typedef struct ListNode {
	struct TreeNode* data;
	struct ListNode* next;
} ListNode;

typedef struct TreeNode {
	eletype data;
	ListNode *childrenHead;
} TreeNode;

void AddTreeChild(TreeNode *parent,TreeNode *child){
	ListNode* childNode=(ListNode*)malloc(sizeof(ListNode));
	childNode->next=NULL;
	childNode->data=child;
	if(parent->childrenHead==NULL){
		parent->childrenHead=childNode;
	} else {
		childNode->next=parent->childrenHead;
		parent->childrenHead=childNode;
	}
}

typedef struct Tree{
	TreeNode *nodes;
	TreeNode *root;
} Tree;

void TreeCreat (Tree *t,int size){
	t->nodes=(TreeNode*)malloc(sizeof(TreeNode)*size);
	t->root=NULL;
}

void TreeDestroy (Tree *t){
	free(t->nodes);
	t->nodes=NULL;
	t->root=NULL; 
}

TreeNode* TreeGetNode(Tree *t,int id){
	return &t->nodes[id];
}

void TreeSetRoot (Tree* t,int id){
	t->root=TreeGetNode(t,id);
}

void TreeAddChild(Tree* t,int parentID,int childID){
	TreeNode* parentNode=TreeGetNode(t,parentID);
	TreeNode* childNode=TreeGetNode(t,childID);
	AddTreeChild(parentNode,childNode);
}

void TreeAssignData(Tree* t,int id,eletype data){
	TreeGetNode(t,id)->data=data;
}

void TreePrint(Tree* t,TreeNode *node){
	if(node==NULL){
		node=t->root; 
	} 
	printf("%c",node->data);
	ListNode *tmp=node->childrenHead;
	while(tmp){
		TreePrint(t,tmp->data);
		tmp=tmp->next;
	}
}

int main(){
	Tree T;
	TreeCreat(&T,9);
	TreeSetRoot(&T,0);

	TreeAssignData(&T,0,'a');
	TreeAssignData(&T,1,'b');
	TreeAssignData(&T,2,'c');
	TreeAssignData(&T,3,'d');
	TreeAssignData(&T,4,'e');
	TreeAssignData(&T,5,'f');
	TreeAssignData(&T,6,'g');
	TreeAssignData(&T,7,'h');
	TreeAssignData(&T,8,'i');
	
	TreeAddChild(&T,0,2);
	TreeAddChild(&T,0,1);
	TreeAddChild(&T,1,3);
	TreeAddChild(&T,2,5);
	TreeAddChild(&T,2,4);
	TreeAddChild(&T,3,8);
	TreeAddChild(&T,3,7);
	TreeAddChild(&T,3,6);
	
	TreePrint(&T,T.root);
	return 0; 
	
	
}

 后记

分布解析的代码好像有点小问题(思路没有问题),应该是有个小的细节错误,但是调试的时候没有找到。后面的完整源码是我重新敲得,没有问题,放心食用。那个小问题后面找到了,会将博客更新的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值