当前位置:   article > 正文

C语言数据结构之树(保姆级讲解)_c语言的图与树

c语言的图与树

前言

         树是“一对多”的非线性存储结构,较链表、队列那些,树的概念会多点,涵盖的范围也较广。

C语言数据结构之单链表

C语言数据结构之双向链表

c语言数据结构之栈

c语言数据结构之队列

1 树概念

1.1  树基本术语

图1

 

 双亲节点:以A、B、C、D为例,A是BCD的双亲节点;

孩子节点:以A、B、C、D为例,BCD是A的子节点;

兄弟节点:BCD有共同的父节点A,所以BCD之间又互为兄弟节点;

叶子节点:没有孩子节点的节点,例如K、L、M节点;

根节点:没有双亲结点的节点;

子树:A是整棵树的树根节点,单看BEFKL节点,也可以看成单独的树,B节点为该树的树根,                 所以BEFKL节点称为子树;

:由根节点和若干子树构成;单个节点也可以看成树,只有一个树根节点;

节点的度:该节点拥有孩子节点的个数,A节点的度为3;

树的度:比较树中各节点的度,最大节点的度为该树的度,以上的树的度为3;

树的深度(高度):根节点为第一层,然后依此类推,KLM在第四层,所以,以上树深度为4;

有序树:树中各节点位置不能互换的叫有序树;无序树,节点位置则可以互换;

空树:没有任何节点;

森林:由 n (n>=0)个互不相交的树组成的叫森林;以上去除 A 节点,分别以 B、C、D 为根节点的三棵子树就可以称为森林。

1.2  二叉树

二叉树:顾名思义,就是子节点的个数不能超过2个的有序树,只能0、1和2;二叉树又可以细分为满二叉树和完全二叉树;

满二叉树:二叉树中除了叶子结点,每个结点的度都为 2,则称为满二叉树;

完全二叉树:一个深度为k,有n个节点的二叉树中,节点按从上至下、从左到右的顺序进行编号,编号为 i (1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同则为完全二叉树。

 2 二叉树节点的遍历

图二
图2

 

2.1 前序遍历

公式:先“根”节点,再左子树,后右子树

此处说的“根”节点并不是特指树的根节点,如图2,由树的根节点A开始遍历,然后遍历左节点B,然后以B节点作为“根”节点,继续套用公式,再左后右,所以下一个遍历D节点,直到叶子节点处不再有子节点,就往回找右节点,以此类推......

图2的前序遍历最终顺序为:A->B->D->H->E->C->F->I->G

2.2 中序遍历

公式:先左子树,再“根”节点,后右子树

如图2,把该树看成由根节点A和左、右子树组成的一棵树;先看左子树:套公式,先左,从H节点开始,再“根”遍历D,该“根”没有右节点,则继续往上找(在D、H组成的子树中,D为根,H为左;而在B、D、E组成的子树中,B为根,D为左,E为右节点),接下来遍历“根”节点B,后遍历右节点E,以此类推......

图2的中序遍历最终顺序为:H->D->B->E->A->I->F->C->G

2.3 后序遍历

 公式:先左子树,再右子树,后根节点

后序遍历就不多解释了,跟前两个差不多逻辑,

图2的后序遍历最终顺序为:H->D->E->B->I->F->G->C->A

3 树实例编写

3.1  实现逻辑概要

接下来我们要实现的树形结构并非二叉树,双亲节点下可以有多个孩子节点;

功能:

        ①可以在树形结构中,指定的节点下添加孩子节点;

        ②遍历树中节点,找出指定节点的双亲节点和孩子节点,并打印在终端;

        ③退出程序并释放节点内存空间。

3.2  程序分析

3.2.1  定义相关结构体

节点数据最大不能超过32个字节;

struct _bro结构体,用来链接双亲节点下的各兄弟节点;

struct _node结构体中,data指向节点具体数据,parent指向该节点的双亲节点,head_child指向首个孩子节点,child_count表示该节点下的孩子节点个数,cur_high表示该节点所在树结构中的层数;

struct _tree结构体,包含一个节点变量和树的深度(tree_high)。

  1. typedef char Data_type; //数据类型
  2. #define DATA_LEN_MAX 32
  3. typedef struct _bro { //亲兄弟
  4. struct _bro *next;
  5. }Bro_t;
  6. typedef struct _node {
  7. Data_type *data; //节点数据
  8. struct _node *parent; //双亲节点
  9. struct _node *head_child; //孩子节点
  10. int child_count; //孩子个数
  11. Bro_t bro_list; //亲兄弟节点链表
  12. int cur_high; //当前节点层次,根节点为1
  13. }Node_t;
  14. typedef struct _tree {
  15. Node_t *node; //树的节点
  16. int tree_high; //树的高度
  17. }Tree_t;

3.2.2 节点位置遍历函数

传入参数:树和双亲结点数据;

通过比较节点数据找到双亲节点,并将该节点地址值返回;

查找的方法就是二叉树的先序遍历方法,先根节点,再左子树,后右子树,直到找到为止,在这里兄弟节点的链表就发挥了很大作用。

  1. /*****************************************
  2. * Fuction :查找双亲节点(类似前序遍历)
  3. * root :传递树根
  4. * Pdata :传递查找的父节点数据
  5. * ***************************************/
  6. Node_t *find_parent_node(Tree_t *root,Data_type *Pdata)
  7. {
  8. Node_t *p = root->node;
  9. while(1) {
  10. if(strcmp(p->data,Pdata) == 0){ //找到节点 退出
  11. //printf("find parent node succeed .\n");
  12. return p;
  13. }
  14. else{
  15. if(p->head_child != NULL) { //判断该节点是否有子节点
  16. p = p->head_child;
  17. }
  18. else {
  19. if(p->bro_list.next != NULL) { //没有子节点,则判断是否有兄弟节点
  20. p = (Node_t *)p->bro_list.next;
  21. }
  22. else {
  23. while(1) {
  24. p = p->parent;
  25. if(p->parent == NULL) { //往树根节点方向查找还未遍历的节点,直到树根处退出遍历
  26. printf("find parent node fail !\n");
  27. return NULL;
  28. }
  29. if(p->bro_list.next != NULL) {
  30. p = (Node_t *)p->bro_list.next;
  31. break;
  32. }
  33. }
  34. }
  35. }
  36. }
  37. }
  38. }

3.2.3 兄弟链表函数

参数:传递新添加的节点;

双亲节点的head_child作为兄弟链表表头,表尾node->bro_list.next指向NULL表示结束。

  1. /********************************
  2. * Fuction :链接亲兄弟节点
  3. * node :添加的节点
  4. * ******************************/
  5. void bro_node_list(Node_t *node)
  6. {
  7. Node_t *p = node->parent;
  8. Node_t *q = p->head_child;
  9. if(p->child_count == 0) {
  10. p->head_child = node;
  11. node->bro_list.next = NULL;
  12. p->child_count += 1;
  13. }
  14. else {
  15. while(q->bro_list.next != NULL){
  16. q = (Node_t *)q->bro_list.next;
  17. }
  18. q->bro_list.next = (Bro_t *)node;
  19. node->bro_list.next = NULL;
  20. }
  21. }

3.2.4  添加节点函数

这里需要传递三个参数:树,父节点数据,新节点数据;

首先,需要为所创建的节点申请结构体变量内存空间和节点数据存放空间,内存申请失败则直接退出创建;如果创建新节点之前为空树,则新创建的节点作为树根节点,否则就通过函数find_parent_node查找父节点的位置,查找失败退出,成功就添加新节点,并将新节点添加到兄弟链表中。

该函数主要做的就是填充结构体变量,使各节点联系起来。

  1. /*************************************************
  2. * Fuciton :在树中创建一个新节点,新节点挂在双亲节点下
  3. * root :传递树根节点
  4. * Pdata :双亲节点的数据
  5. * Data :新节点的数据
  6. * ***********************************************/
  7. void create_tree_node(Tree_t *root, Data_type *Pdata, Data_type *Data)
  8. {
  9. Node_t *node = (Node_t *)malloc(sizeof(Node_t));
  10. Node_t *p = NULL;
  11. Data_type *data_buf = (Data_type *)malloc(strlen(Data)+ 1);
  12. if((node == NULL) || (data_buf == NULL)) {
  13. printf("malloc failed !\n");
  14. return ;
  15. }
  16. strcpy(data_buf,Data);
  17. if(root->node == NULL) { //树根
  18. root->node = node;
  19. root->tree_high = 1;
  20. node->data = data_buf;
  21. node->parent = NULL;
  22. node->head_child = NULL;
  23. node->child_count = 0;
  24. node->bro_list.next = NULL;
  25. node->cur_high = 1;
  26. }
  27. else {
  28. p = find_parent_node(root,Pdata);
  29. if(p != NULL) { //找到双亲节点
  30. node->data = data_buf;
  31. node->parent = p;
  32. node->head_child = NULL;
  33. node->child_count = 0;
  34. node->cur_high = p->cur_high + 1;
  35. if(node->cur_high > root->tree_high) {
  36. root->tree_high = node->cur_high;
  37. }
  38. bro_node_list(node);
  39. }
  40. else {
  41. free(node);
  42. free(data_buf);
  43. }
  44. }
  45. }

3.2.5 节点内存释放函数

参数:传递树;

节点的内存释放只能从叶子节点开始,不然各节点的联系就会被破坏,从而无法找到其它节点,这里利用后序遍历法进行查找,先左子树,再右子树,后根节点;

通过for(p; p->head_child != NULL; p = p->head_child)查找树的最终左子树,然后判断该节点在兄弟链表中是否有其它的节点,如果有,则去查找该子树的叶子节点,从叶子节点开始释放,没有的话就释放掉,回到双亲节点继续查找。

  1. /**********************************
  2. * Fuction :释放掉节点申请的内存(后序遍历方法)
  3. * root :传递树根节点
  4. * *******************************/
  5. void free_tree_nodes(Tree_t *root)
  6. {
  7. Node_t *p = root->node;
  8. Node_t *temp = NULL;
  9. int free_count = 0;
  10. if(p == NULL) {
  11. printf("empty tree! \n");
  12. return;
  13. }
  14. for(p; p->head_child != NULL; p = p->head_child); //查找左子树的叶子节点
  15. do {
  16. if(p->bro_list.next != NULL) { //判断该节点是否有兄弟节点
  17. temp = p;
  18. printf(" free data is %s\n",temp->data);
  19. p = (Node_t *)p->bro_list.next;
  20. free(temp);
  21. for(p; p->head_child != NULL; p = p->head_child);
  22. }
  23. else {
  24. temp = p;
  25. printf(" free data is %s\n",temp->data);
  26. p = p->parent;
  27. free(temp);
  28. }
  29. ++free_count;
  30. }while(p != NULL);
  31. printf("%d-Node memory free OK.\n",free_count);
  32. }

3.3 程序汇总

  1. /****************************************
  2. Fuction:C语言实现树
  3. Time:11/21/2022
  4. Author:Qurry
  5. ****************************************/
  6. #include <stdio.h>
  7. #include <stdlib.h>
  8. #include <string.h>
  9. typedef char Data_type; //数据类型
  10. #define DATA_LEN_MAX 32
  11. typedef struct _bro { //亲兄弟
  12. struct _bro *next;
  13. }Bro_t;
  14. typedef struct _node {
  15. Data_type *data; //节点数据
  16. struct _node *parent; //双亲节点
  17. struct _node *head_child; //孩子节点
  18. int child_count; //孩子个数
  19. Bro_t bro_list; //亲兄弟节点链表
  20. int cur_high; //当前节点层次,根节点为1
  21. }Node_t;
  22. typedef struct _tree {
  23. Node_t *node; //树的节点
  24. int tree_high; //树的高度
  25. }Tree_t;
  26. /*****************************************
  27. * Fuction :查找双亲节点(类似前序遍历)
  28. * root :传递树根
  29. * Pdata :传递查找的父节点数据
  30. * ***************************************/
  31. Node_t *find_parent_node(Tree_t *root,Data_type *Pdata)
  32. {
  33. Node_t *p = root->node;
  34. while(1) {
  35. if(strcmp(p->data,Pdata) == 0){ //找到节点 退出
  36. //printf("find parent node succeed .\n");
  37. return p;
  38. }
  39. else{
  40. if(p->head_child != NULL) { //判断该节点是否有子节点
  41. p = p->head_child;
  42. }
  43. else {
  44. if(p->bro_list.next != NULL) { //没有子节点,则判断是否有兄弟节点
  45. p = (Node_t *)p->bro_list.next;
  46. }
  47. else {
  48. while(1) {
  49. p = p->parent;
  50. if(p->parent == NULL) { //往树根节点方向查找还未遍历的节点,直到树根处退出遍历
  51. printf("find parent node fail !\n");
  52. return NULL;
  53. }
  54. if(p->bro_list.next != NULL) {
  55. p = (Node_t *)p->bro_list.next;
  56. break;
  57. }
  58. }
  59. }
  60. }
  61. }
  62. }
  63. }
  64. /********************************
  65. * Fuction :链接亲兄弟节点
  66. * node :添加的节点
  67. * ******************************/
  68. void bro_node_list(Node_t *node)
  69. {
  70. Node_t *p = node->parent;
  71. Node_t *q = p->head_child;
  72. if(p->child_count == 0) {
  73. p->head_child = node;
  74. node->bro_list.next = NULL;
  75. p->child_count += 1;
  76. }
  77. else {
  78. while(q->bro_list.next != NULL){
  79. q = (Node_t *)q->bro_list.next;
  80. }
  81. q->bro_list.next = (Bro_t *)node;
  82. node->bro_list.next = NULL;
  83. }
  84. }
  85. /*************************************************
  86. * Fuciton :在树中创建一个新节点,新节点挂在双亲节点下
  87. * root :传递树根节点
  88. * Pdata :双亲节点的数据
  89. * Data :新节点的数据
  90. * ***********************************************/
  91. void create_tree_node(Tree_t *root, Data_type *Pdata, Data_type *Data)
  92. {
  93. Node_t *node = (Node_t *)malloc(sizeof(Node_t));
  94. Node_t *p = NULL;
  95. Data_type *data_buf = (Data_type *)malloc(strlen(Data)+ 1);
  96. if((node == NULL) || (data_buf == NULL)) {
  97. printf("malloc failed !\n");
  98. return ;
  99. }
  100. strcpy(data_buf,Data);
  101. if(root->node == NULL) { //树根
  102. root->node = node;
  103. root->tree_high = 1;
  104. node->data = data_buf;
  105. node->parent = NULL;
  106. node->head_child = NULL;
  107. node->child_count = 0;
  108. node->bro_list.next = NULL;
  109. node->cur_high = 1;
  110. }
  111. else {
  112. p = find_parent_node(root,Pdata);
  113. if(p != NULL) { //找到双亲节点
  114. node->data = data_buf;
  115. node->parent = p;
  116. node->head_child = NULL;
  117. node->child_count = 0;
  118. node->cur_high = p->cur_high + 1;
  119. if(node->cur_high > root->tree_high) {
  120. root->tree_high = node->cur_high;
  121. }
  122. bro_node_list(node);
  123. }
  124. else {
  125. free(node);
  126. free(data_buf);
  127. }
  128. }
  129. }
  130. /**********************************
  131. * Fuction :添加多个子树节点
  132. * root :传递树根节点
  133. * *******************************/
  134. void add_tree_nodes(Tree_t *root)
  135. {
  136. Data_type pdata_buf[DATA_LEN_MAX],data_buf[DATA_LEN_MAX];
  137. memset(pdata_buf,0,DATA_LEN_MAX);
  138. memset(data_buf,0,DATA_LEN_MAX);
  139. printf("window下输入ctrl+z完成创建;linux下输入ctrl+d完成创建\n");
  140. if(root->node == NULL){
  141. printf("please input tree root data : \n");
  142. scanf("%s",data_buf);
  143. create_tree_node(root, NULL, data_buf);
  144. }
  145. printf("please input Pdata & data :\n");
  146. while(scanf("%s %s",pdata_buf,data_buf) != EOF) {
  147. printf("pdata_buf:%s,data_buf:%s \n",pdata_buf,data_buf);
  148. create_tree_node(root, pdata_buf, data_buf);
  149. }
  150. printf("tree high is %d\n",root->tree_high);
  151. }
  152. /**********************************
  153. * Fuction :遍历打印树形结构
  154. * root :传递树根节点
  155. * *******************************/
  156. void print_tree_node(Tree_t *root)
  157. {
  158. Data_type temp[DATA_LEN_MAX];
  159. Node_t *p = NULL;
  160. int i = 0;
  161. printf("please input node data :");
  162. scanf("%s",temp);
  163. p = find_parent_node(root,temp);
  164. if(p == NULL) {
  165. printf("node data error! \n");
  166. }
  167. else {
  168. if(p->parent != NULL)
  169. printf("Node parent data :%s\n",p->parent->data);
  170. if(p->head_child != NULL) {
  171. p = p->head_child;
  172. while(1) {
  173. printf("Node child-%d:%s\n",i++,p->data);
  174. if(p->bro_list.next == NULL)
  175. break;
  176. p = (Node_t *)p->bro_list.next;
  177. }
  178. }
  179. }
  180. }
  181. /**********************************
  182. * Fuction :释放掉节点申请的内存(后序遍历方法)
  183. * root :传递树根节点
  184. * *******************************/
  185. void free_tree_nodes(Tree_t *root)
  186. {
  187. Node_t *p = root->node;
  188. Node_t *temp = NULL;
  189. int free_count = 0;
  190. if(p == NULL) {
  191. printf("empty tree! \n");
  192. return;
  193. }
  194. for(p; p->head_child != NULL; p = p->head_child); //查找左子树的叶子节点
  195. do {
  196. if(p->bro_list.next != NULL) { //判断该节点是否有兄弟节点
  197. temp = p;
  198. printf(" free data is %s\n",temp->data);
  199. p = (Node_t *)p->bro_list.next;
  200. free(temp);
  201. for(p; p->head_child != NULL; p = p->head_child);
  202. }
  203. else {
  204. temp = p;
  205. printf(" free data is %s\n",temp->data);
  206. p = p->parent;
  207. free(temp);
  208. }
  209. ++free_count;
  210. }while(p != NULL);
  211. printf("%d-Node memory free OK.\n",free_count);
  212. }
  213. /*****************************
  214. * Fuction : 模式菜单
  215. * **************************/
  216. int menu_select()
  217. {
  218. char mode[10];
  219. printf("1:添加 2:打印 0:退出\n");
  220. printf("Please input mode num: ");
  221. scanf("%s",&mode);
  222. if(mode[0] == '1')
  223. return 1;
  224. else if(mode[0] == '2')
  225. return 2;
  226. else if(mode[0] == '0')
  227. return 0;
  228. else
  229. return -1;
  230. }
  231. void main()
  232. {
  233. int mode = -1;
  234. Tree_t tree_root = {
  235. .node = NULL,
  236. .tree_high = 0,
  237. };
  238. Tree_t *tree_p = &tree_root;
  239. while(1) {
  240. mode = menu_select();
  241. switch(mode)
  242. {
  243. case 0:
  244. free_tree_nodes(tree_p);
  245. return ;
  246. case 1:
  247. add_tree_nodes(tree_p);
  248. break;
  249. case 2:
  250. print_tree_node(tree_p);
  251. break;
  252. default :
  253. printf("mode input error !\n");
  254. break;
  255. }
  256. }
  257. }

4 测试结果

按照上图 图1 的树形结构进行测试,结果如下:

 

 

 

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号