在线性内存中如何实现对非线性事物的存储。

树的定义:

树:一种非线性的数据结构
树是n(n >= 0)个结点的有限级 . n=0时称为空树,
在任意一棵非空树中:
    a)有且只有一个特定的称为根的结点,它只有直接后继,但没有直接前驱
    b)当n>1,其余结点可分为m (m >0)个互不相交的有限集 T1、T2...,其中每一个集合本身又是一棵树,并且称为根的子树。

结点分类:

树的结点包含一个数据元素及若干指向子树的分支。

结点拥有的子树数称为结点的度
    度为0 的结点称为叶结点
    度不为0 的结点称为分支结点
树的度定义为所有结点中的度的最大值

因为这棵树结点D的为3,为这个树上结点的最大值; 所以此树的度为3

结点间关系:

结点的直接后继称为该结点的孩子,相应的,该结点称为孩子的双亲。
以某结点为根的子树中的任一结点都称为该结点的子孙,相应的,该结点称为子孙的祖先
同一双亲的孩子之间称为兄弟。


结点的层次:

根为第一层
根的孩子为第2层
.....



树的深度:

对于任一结点Ni, Ni的深度为从根到Ni的唯一路径长。如,根的深度为0

树的高度:

从Ni到一片树叶最长路径的长。如,所有的树叶(叶节点)的高都为0
一棵树的深度等于它的最深的树叶(叶节点)的深度,该深度总是等于这棵树的高

有序树与无序树的概念:

如果树中结点的各子树从左到右是有次序的,子树间不能互换位置,则称该树为有序树,否则称该树为无序树

森林的概念:

森林是有N (N >= 0 )棵 互不相交的树组成。

由3棵树组成的森林。

树的操作:

创建树-- tree_create
销毁树-- tree_destroy
清空树-- tree_clear
插入结点-- tree_insert
删除结点-- tree_delete
获取结点-- tree_get
获取根结点-- tree_root
获取树的结点数-- tree_count
获取树的度-- tree_degree

/* 创建树 */
gtree* gtree_create();

/* 销毁已存在的树 */
void gtree_destroy(gtree* tree);

/* 将已存在的树清空为空树 */
void gtree_clear(gtree* tree);

/* 将结点node插入到tree中的pos位置处 */
int gtree_insert(gtree* tree, gtree_data* data, int pos);

/* 将tree中pos位置的结点删除并返回 */
gtree_data* gtree_delete(gtree* tree, int pos);

/* 将tree中pos位置的结点返回 */
gtree_data* gtree_get(gtree* tree, int pos);

/* 返回tree的根结点 */
gtree_data* gtree_root(gtree* tree);

/* 返回tree的高度 */
int gtree_height(gtree* tree);

/* 返回树的结点数 */
int gtree_count(gtree* tree);

/* 返回树的度数 */
int gtree_degree(gtree* tree);

/*打印树的结构*/
void gtree_display(gtree* tree, gtree_printf* p_func,char div);




树的存储结构:


值得指出的是
这样利用链表组织了树中的各个结点
•链表中的前后关系不代表结点间的逻辑关系
•结点的逻辑关系有数据域child描述
•child数据域保存了子结点的存储地址

同时由图可知,若插入结点,需要将待插入的结点 插入两个链表中去,一个为 各个结点组成的链表,另一个为插入其 父结点的孩子链表,当然也存在父结点不存在的可能性--根结点。

通用树具体结构的定义:
/*树节点结构体*/
typedef struct _tag_gtree_node gtree_node;
struct _tag_gtree_node {
    gtree_data *data;
    gtree_node *parent;  //指向父节点的指针
    link_list* child; //结点孩子的链表
};

/*链表结点结构体*/
typedef struct _tag_t_node gtree;
struct _tag_t_node {
    link_list_node header;
    gtree_node* node;
};


插入操作:
/* 将结点node插入到gtree中的pos位置处  
pos:即是插入结点双亲在链表中的位置*/
int gtree_insert(gtree* tree, gtree_data* data, int pos)
{
 /*接收树链表 */
 link_list* list = (link_list*)tree;
 
 /*检测接收的链表、数据、父节点的范围是否有效*/
 int ret = (list != NULL) && (data != NULL) && (pos < link_list_length(list));
 
 if(ret)
 {
  /*为数据申请结点*/
  g_tree_node* c_node = (g_tree_node *)malloc(sizeof(g_tree_node));
 
  /*为树链表结点中申请空间*/
  T_tree_node* Tr_node = (T_tree_node *)malloc(sizeof(T_tree_node));
 
  /*为树链表中结点的子结点申请空间*/
  T_tree_node* cld_node = (T_tree_node *)malloc(sizeof(T_tree_node));
 
  /*根据父结点的位置pos找出父结点*/
  T_tree_node* p_node = (T_tree_node*)link_list_get(list, pos);
 
  /*检验申请的空间的合法性*/
  ret =  (c_node != NULL) && (Tr_node != NULL) && (cld_node != NULL);
 
  if(ret)
  {
   c_node->data = data;
   
   /*不确定父结点是否存在,先设为空*/
   c_node->parent = NULL;
   c_node->child = link_list_create();
   
   Tr_node->node = c_node;
   cld_node->node = c_node;
   
   link_list_insert(list, (link_listnode *)Tr_node, link_list_length(list));
   
    /*父结点存在*/
   if(p_node != NULL)
   {
    c_node->parent = p_node->node;
    link_list_insert(p_node->node->child, (link_listnode *)cld_node, link_list_length(p_node->node->child));
   }
    else
   {
    /*不存在则释放申请的结点*/
    free(cld_node);
   }
   
 
  }
  else
  {
   free(c_node);
   free(Tr_node);
   free(cld_node);
  }    
    
 } 
 
}



打印函数:
在实现链表的插入操作之后,为了直观的观察存放在内存中的树的结构,新建一个打印函数,以文本的形式去展示一棵树。使用缩进格式来体现父子结点之间的关系(如文件系统树目录一样)
打印函数的 递归实现:
在树链表中把根结点取出,遍历各个结点,打印出各个结点的子节点。

实现一:未优化,待代码可重用性不高。
/*以format的格式递归打印,*/
static void recursive_display(g_tree_node* node, int format)
{
 int i;
 
 if(node != NULL)
 {
  for(i = 0; i < format; i++)
  {
   printf("%c", ' ');
  }
 
  printf("%c", (int)(node->data));

  printf("\n");
 
/*遍历该结点的孩子,打印孩子的数据*/
  for(i = 0; i < link_list_length(node->child); i++)
  {
   T_tree_node* cld_node = (T_tree_node*)link_list_get(node->child, i);
   
   recursive_display(cld_node->node, format+1);
  }
 
 }
} 
/*打印函数*/
void gtree_display(gtree* tree)
{
/*从根结点开始遍历打印*/
 T_tree_node* Tr_node = (T_tree_node*)link_list_get(tree, 0);
 
 if(Tr_node != NULL)
 {
  recursive_display(Tr_node->node, 0);
 }
}



实现二:代码可重用性更高,用户选择性更高
/*以format的格式递归打印 
*p_func:函数指针,打印不同的数据类型
*node:待打印结点
*format:打印格式,即缩进多少
*div:缩进的字符
*/
static void recursive_display(g_tree_node* node, gtree_printf* p_func, int format, char div)
{
 int i;
 
 if(node != NULL)
 {
  for(i = 0; i < format; i++)
  {
   printf("%c", div);
  }
 
  p_func(node->data);
  printf("\n");
 
  /*遍历该结点的孩子,打印孩子的数据*/
  for(i = 0; i < link_list_length(node->child); i++)
  {
   T_tree_node* cld_node = (T_tree_node*)link_list_get(node->child, i);
   
   recursive_display(cld_node->node, p_func, format+1, div);
  }
 
 }
}
void gtree_display(gtree* tree, gtree_printf* p_func, char div)
{
 T_tree_node* Tr_node = (T_tree_node*)link_list_get(tree, 0);
 
 if(Tr_node != NULL)
 {
  recursive_display(Tr_node->node, p_func, 0, div);
 }
}


删除操作:
思路:我们知道在实现树的时候是使用链表来存储 树中的每一个结点,可以先遍历去找到需要删掉的结点,返回结点内数据域的值。
又因为在插入操作中,结点被插入到了两条链表中了(树链表、父结点的孩子链表)
所以,应将两条链表中相应的结点依次删除。
    对应步骤:
             •遍历树链表,将该结点对应项删除
            •找到父结点,把其子结点链表的中的对应项删除
            •找到该结点的孩子链表,递归删除
/*递归删除*/ 
static void recursive_delete(link_list *list, g_tree_node* node)
{
 int i = 0;
 
 /*标记被删除的结点在树链表中的位置*/
 int index = -1;
 
 if((list != NULL ) && (node != NULL))
 {
  g_tree_node* parent = node->parent;
 
  /*删除树链表中的结点*/
  for( i = 0; i < link_list_length(list); i++)
  {
   T_tree_node* Tr_node = (T_tree_node*)link_list_get(list,i);
   
   if(Tr_node->node == node)
   {
    link_list_delete(list, i);
    free(Tr_node);
    index = i;
    break;
   }
   
  }
 
  /*删除父结点的子链表中对应该结点的位置*/
   if(index >= 0)
   {
   if(parent != NULL)
   {
    for(i=0; i < link_list_length(parent->child); i++)
    {
     T_tree_node* Tr_node = (T_tree_node*)link_list_get(parent->child,i);
     
     if(Tr_node->node == node)
     {
      link_list_delete(parent->child, i);
      free(Tr_node);
      break;
     }
    }
     
   }
   }
   
   /*删除结点的孩子链表中元素*/
   while(link_list_length(node->child))
   {
   T_tree_node* Tr_node = (T_tree_node*)link_list_get(node->child, 0);
   
   /*递归删除子结点*/
   recursive_delete(node->child, Tr_node->node);
   }
   
  link_list_destroy(node->child);
 
  free(node);
 
 }
 
}
/* 将gtree中pos位置的结点删除并返回
*注:若删除的结点还有子结点,同时也将其子结点删除
*/
gtree_data* gtree_delete(gtree* tree, int pos)
{
 /*在构建的树链表中找到删除的结点*/
 T_tree_node* Tr_node = (T_tree_node*)link_list_get(tree, pos);
 gtree_data* ret = NULL;
 
 if(Tr_node != NULL)
 {
  ret = Tr_node->node->data;
  recursive_delete(tree, Tr_node->node);
 }
 
 return ret;
} 


获取树的高度:
同上面一样,因为要遍历子树,所以采用递归的方案
基准情形:遍历到叶节点,高度为一
不断推进:从根结点开始遍历,之后找到其子树的根结点,再由子树的根结点遍历其子结点是否还有子树,递归下去..
static int recursive_height(g_tree_node* node)
{
 int ret = 0;
 
 if( node != NULL)
 {
  int sub_height = 0;
  int i = 0;
 
  for(i=0; i<link_list_length(node->child); i++)
  {
   T_tree_node* Tr_node = (T_tree_node*)link_list_get(node->child, i);
   
   sub_height = recursive_height(Tr_node->node);
   
   if(ret < sub_height)
   {
    ret = sub_height;
   }
  }
 
  /*其自身节点的高度*/
  ret = ret + 1;
 }
 return ret;
}
/* 返回gtree的高度 */
int gtree_height(gtree* tree)
{
 int ret = 0;
 T_tree_node* Tr_node = (T_tree_node*)link_list_get(tree, 0);
 
 if(Tr_node != NULL)
 {
   ret = recursive_height(Tr_node->node);
 }
 
 return ret;
}


获取树的深度:
思路同获取树的高度一样
static int recursive_degree(g_tree_node* node)
{
 int ret = 0;
 
 if(node != NULL)
 {
  int sub_degree	= 0;
  int i = 0;
 
  ret = link_list_length(node->child);
 
  for(i = 0; i < link_list_length(node->child); i++)
  {
   T_tree_node* Tr_node = (T_tree_node*)link_list_get(node->child, i);
   
   sub_degree = recursive_degree(Tr_node->node);
   
   if(ret < sub_degree)
   {
    ret = sub_degree;
   }
  }
 }
 
 return ret;
}
/* 返回树的度数 */
int gtree_degree(gtree* tree)
{
 T_tree_node* Tr_node = (T_tree_node*)link_list_get(tree, 0);
 int ret = 0;
 
 if(Tr_node != NULL)
 {
  ret = recursive_degree(Tr_node->node);
 }
 
 return ret;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值