ExcelHome技术论坛

 找回密码
 免费注册

QQ登录

只需一步,快速开始

快捷登录

搜索
EH技术汇-专业的职场技能充电站 妙哉!函数段子手趣味讲函数 Excel服务器-会Excel,做管理系统 效率神器,一键搞定繁琐工作
HR薪酬管理数字化实战 Excel 2021函数公式学习大典 Excel数据透视表实战秘技 打造核心竞争力的职场宝典
让更多数据处理,一键完成 数据工作者的案头书 免费直播课集锦 ExcelHome出品 - VBA代码宝免费下载
用ChatGPT与VBA一键搞定Excel WPS表格从入门到精通 Excel VBA经典代码实践指南
查看: 1845|回复: 12

[求助] VBA实现多叉树的设计、建立、层次优先遍历和深度优先遍历

[复制链接]

TA的精华主题

TA的得分主题

发表于 2023-1-16 13:36 | 显示全部楼层 |阅读模式

缘起:
https://www.cnblogs.com/unixfy/p/3486179.html
多叉树的设计、建立、层次优先遍历和深度优先遍历
搜索一下各种类似文章代码似乎都是其他语言的,对于只知VBA,而对其他语言小白来说,
看了以后,大致一知半解,恳请那位老师,能用VBA实现该功能呢:
多叉树最长路径.png
  1. // 多叉树的建立、层次遍历、深度遍历
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #define M 100+1 // 宏定义,定义最大名字字母长度
  6. // 定义多叉树的节点结构体
  7. typedef struct node_t
  8. {
  9.     char* name;               // 节点名
  10.     int   n_children;         // 子节点个数
  11.     int   level;              // 记录该节点在多叉树中的层数
  12.     struct node_t** children; // 指向其自身的子节点,children一个数组,该数组中的元素时node_t*指针
  13. } NODE; // 对结构体重命名
  14. // 实现一个栈,用于后续操作
  15. typedef struct stack_t
  16. {
  17.     NODE** array; // array是个数组,其元素为NODE*型指针
  18.     int    index; // 指示栈顶元素
  19.     int    size;  // 栈的大小
  20. } STACK; // 重命名
  21. // 实现一个队列,用于后续操作
  22. typedef struct queue_t
  23. {
  24.     NODE** array; // array是个数组,其内部元素为NODE*型指针
  25.     int    head;  // 队列的头
  26.     int    tail;  // 队列的尾
  27.     int    num;   // 队列中元素的个数
  28.     int    size;  // 队列的大小
  29. } QUEUE;
  30. // 这里的栈和队列,都是用动态数组实现的,另一种实现方式是用链表
  31. // 内存分配函数
  32. void* util_malloc(int size)
  33. {
  34.     void* ptr = malloc(size);
  35. if (ptr == NULL) // 如果分配失败,则终止程序
  36.     {
  37.         printf("Memory allocation error!\n");
  38.         exit(EXIT_FAILURE);
  39.     }
  40. // 分配成功,则返回
  41.     return ptr;
  42. }
  43. // 字符串赋值函数
  44. // 对strdup函数的封装,strdup函数直接进行字符串赋值,不用对被赋值指针分配空间
  45. // 比strcpy用起来方便,但其不是标准库里面的函数
  46. // 用strdup函数赋值的指针,在最后也是需要free掉的
  47. char* util_strdup(char* src)
  48. {
  49.     char* dst = strdup(src);
  50. if (dst == NULL) // 如果赋值失败,则终止程序
  51.     {
  52.         printf ("Memroy allocation error!\n");
  53.         exit(EXIT_FAILURE);
  54.     }
  55. // 赋值成功,返回
  56.     return dst;
  57. }
  58. // 对fopen函数封装
  59. FILE* util_fopen(char* name, char* access)
  60. {
  61.     FILE* fp = fopen(name, access);
  62. if (fp == NULL) // 如果打开文件失败,终止程序
  63.     {
  64.         printf("Error opening file %s!\n", name);
  65.         exit(EXIT_FAILURE);
  66.     }
  67. // 打开成功,返回
  68.     return  fp;
  69. }
  70. // 实现一些栈操作
  71. // 栈的初始化
  72. STACK* STACKinit(int size) // 初始化栈大小为size
  73. {
  74.     STACK* sp;
  75. sp = (STACK*)util_malloc(sizeof (STACK));
  76.     sp->size  = size;
  77.     sp->index = 0;
  78.     sp->array = (NODE**)util_malloc(size * sizeof (NODE*));
  79. return sp;
  80. }
  81. // 检测栈是否为空
  82. // 如果为空返回1,否则返回0
  83. int STACKempty(STACK* sp)
  84. {
  85.     if (sp == NULL || sp->index <= 0) // 空
  86.     {
  87.         return 1;
  88.     }
  89.     return 0;
  90. }
  91. // 压栈操作
  92. int STACKpush(STACK* sp, NODE* data)
  93. {
  94.     if (sp == NULL || sp->index >= sp->size) // sp没有被初始化,或者已满
  95.     {
  96.         return 0; // 压栈失败
  97.     }
  98. sp->array[sp->index++] = data; // 压栈
  99.     return 1;
  100. }
  101. // 弹栈操作
  102. int STACKpop(STACK* sp, NODE** data_ptr)
  103. {
  104.     if (sp == NULL || sp->index <= 0) // sp为初始化,或者为空没有元素
  105.     {
  106.         return 0;
  107.     }
  108. *data_ptr = sp->array[--sp->index]; // 弹栈
  109.     return 1;
  110. }
  111. // 将栈消毁
  112. void STACKdestroy(STACK* sp)
  113. {
  114.     free(sp->array);
  115.     free(sp);
  116. }
  117. // 以上是栈的操作
  118. // 实现队列的操作
  119. QUEUE* QUEUEinit(int size)
  120. {
  121.     QUEUE* qp;
  122. qp = (QUEUE*)util_malloc(sizeof (QUEUE));
  123.     qp->size  = size;
  124.     qp->head  = qp->tail = qp->num = 0;
  125.     qp->array = (NODE**)util_malloc(size * sizeof (NODE*));
  126. return qp;
  127. }
  128. // 入队列
  129. int QUEUEenqueue(QUEUE* qp, NODE* data)
  130. {
  131.     if (qp == NULL || qp->num >= qp->size) // qp未初始化或已满
  132.     {
  133.         return 0; // 入队失败
  134.     }
  135. qp->array[qp->tail] = data; // 入队,tail一直指向最后一个元素的下一个位置
  136.     qp->tail = (qp->tail + 1) % (qp->size); // 循环队列
  137.     ++qp->num;
  138.     return 1;
  139. }
  140. // 出队列
  141. int QUEUEdequeue(QUEUE* qp, NODE** data_ptr)
  142. {
  143.     if (qp == NULL || qp->num <= 0) // qp未初始化或队列内无元素
  144.     {
  145.         return 0;
  146.     }
  147. *data_ptr = qp->array[qp->head]; // 出队
  148.     qp->head = (qp->head + 1) % (qp->size); // 循环队列
  149.     --qp->num;
  150. return 1;
  151. }
  152. // 检测队列是否为空
  153. int QUEUEempty(QUEUE* qp)
  154. {
  155.     if (qp == NULL || qp->num <= 0)
  156.     {
  157.         return 1;
  158.     }
  159. return 0;
  160. }
  161. // 销毁队列
  162. void QUEUEdestroy(QUEUE* qp)
  163. {
  164.     free(qp->array);
  165.     free(qp);
  166. }
  167. // 以上是队列的有关操作实现
  168. // 生成多叉树节点
  169. NODE* create_node()
  170. {
  171.     NODE* q;
  172. q = (NODE*)util_malloc(sizeof (NODE));
  173.     q->n_children = 0;
  174.     q->level      = -1;
  175.     q->children   = NULL;
  176. return q;
  177. }
  178. // 按节点名字查找
  179. NODE* search_node_r(char name[M], NODE* head)
  180. {
  181.     NODE* temp = NULL;
  182.     int i = 0;
  183. if (head != NULL)
  184.     {
  185.         if (strcmp(name, head->name) == 0) // 如果名字匹配
  186.         {
  187.             temp = head;
  188.         }
  189.         else // 如果不匹配,则查找其子节点
  190.         {
  191.             for (i = 0; i < head->n_children && temp == NULL/*如果temp不为空,则结束查找*/; ++i)
  192.             {
  193.                 temp = search_node_r(name, head->children[i]); // 递归查找子节点
  194.             }
  195.         }
  196.     }
  197. return temp; // 将查找到的节点指针返回,也有可能没有找到,此时temp为NULL
  198. }
  199. // 从文件中读取多叉树数据,并建立多叉树
  200. void read_file(NODE** head, char* filename)
  201. {
  202.     NODE* temp = NULL;
  203.     int i = 0, n = 0;
  204.     char name[M], child[M];
  205.     FILE* fp;
  206. fp = util_fopen(filename, "r"); // 打开文件
  207. while (fscanf(fp, "%s %d", name, &n) != EOF) // 先读取节点名字和当前节点的子节点个数
  208.     {
  209.         if (*head == NULL) // 若为空
  210.         {
  211.             temp = *head = create_node();   // 生成一个新节点
  212.             temp->name = util_strdup(name); // 赋名
  213.         }
  214.         else
  215.         {
  216.             temp = search_node_r(name, *head); // 根据name找到节点
  217.             // 这里默认数据文件是正确的,一定可以找到与name匹配的节点
  218.             // 如果不匹配,那么应该忽略本行数据
  219.         }
  220.         // 找到节点后,对子节点进行处理
  221.         temp->n_children = n;
  222.         temp->children   = (NODE**)malloc(n * sizeof (NODE*));
  223.         if (temp->children == NULL) // 分配内存失败
  224.         {
  225.             fprintf(stderr, "Dynamic allocation error!\n");
  226.             exit(EXIT_FAILURE);
  227.         }
  228. // 如果分配成功,则读取后面的子节点,并保存
  229.         for (i = 0; i < n; ++i)
  230.         {
  231.             fscanf(fp, "%s", child); // 读取子节点
  232.             temp->children[i] = create_node(); // 生成子节点
  233.             temp->children[i]->name = util_strdup(child); // 读子节点赋名
  234.         }
  235.     }
  236. // 读取完毕,关闭文件
  237.     fclose(fp);
  238. }
  239. // 实现函数1
  240. // 将多叉树中的节点,按照深度进行输出
  241. // 实质上实现的是层次优先遍历
  242. void f1(NODE* head)
  243. {
  244.     NODE* p = NULL;
  245.     QUEUE* q = NULL; // 定义一个队列
  246.     STACK* s = NULL; // 定义一个栈
  247.     int i = 0;
  248. q = QUEUEinit(100); // 将队列初始化大小为100
  249.     s = STACKinit(100); // 将栈初始化大小为100
  250. head->level = 0; // 根节点的深度为0
  251. // 将根节点入队列
  252.     QUEUEenqueue(q, head);
  253. // 对多叉树中的节点的深度值level进行赋值
  254.     // 采用层次优先遍历方法,借助于队列
  255.     while (QUEUEempty(q) == 0) // 如果队列q不为空
  256.     {
  257.         QUEUEdequeue(q, &p); // 出队列
  258.         for (i = 0; i < p->n_children; ++i)
  259.         {
  260.             p->children[i]->level = p->level + 1; // 对子节点深度进行赋值:父节点深度加1
  261.             QUEUEenqueue(q, p->children[i]);      // 将子节点入队列
  262.         }
  263.         STACKpush(s, p); // 将p入栈
  264.     }
  265. while (STACKempty(s) == 0) // 不为空
  266.     {
  267.         STACKpop(s, &p); // 弹栈
  268.         fprintf(stdout, "   %d %s\n", p->level, p->name);
  269.     }
  270. QUEUEdestroy(q); // 消毁队列
  271.     STACKdestroy(s); // 消毁栈
  272. }
  273. // 实现函数2
  274. // 找到从根节点到叶子节点路径上节点名字字母个数最大的路径
  275. // 实质上实现的是深度优先遍历
  276. void f2(NODE* head, char* str, char** strBest, int level)
  277. {
  278.     int   i   = 0;
  279.     char* tmp = NULL;
  280. if (head == NULL)
  281.     {
  282.         return;
  283.     }
  284. tmp = (char*)util_malloc((strlen(str) + strlen(head->name) + 1/*原程序中未加1*/) * sizeof (char));
  285.     sprintf(tmp, "%s%s", str, head->name);
  286. if (head->n_children == 0)
  287.     {
  288.         if (*strBest == NULL || strlen(tmp) > strlen(*strBest))
  289.         {
  290.             free(*strBest);
  291.             *strBest = util_strdup(tmp);
  292.         }
  293.     }
  294. for (i = 0; i < head->n_children; ++i)
  295.     {
  296.         f2(head->children[i], tmp, strBest, level + 1);
  297.     }
  298. free(tmp);
  299. }
  300. // 消毁树
  301. void free_tree_r(NODE* head)
  302. {
  303.     int i = 0;
  304.     if (head == NULL)
  305.     {
  306.         return;
  307.     }
  308. for (i = 0; i < head->n_children; ++i)
  309.     {
  310.         free_tree_r(head->children[i]);
  311.     }
  312. free(head->name);
  313.     // free(head->children); // 消毁子节点指针数组
  314.     free(head);
  315. }
  316. int main(int argc, char* argv[])
  317. {
  318.     NODE* head = NULL;
  319.     char* strBest = NULL;
  320. if (argc != 2)
  321.     {
  322.         fprintf(stderr, "Missing parameters!\n");
  323.         exit(EXIT_FAILURE);
  324.     }
  325. read_file(&head, argv[1]);
  326. fprintf(stdout, "f1:\n");
  327.     f1(head);
  328.     f2(head, "", &strBest, 0);
  329.     fprintf(stdout, "f2:\n   %s\n", strBest);
  330. free_tree_r(head);
  331. return EXIT_SUCCESS;
  332. }
复制代码


谢谢!






TA的精华主题

TA的得分主题

发表于 2023-1-16 22:43 | 显示全部楼层
该题用VBA写,应该没这么复杂,估计不超过50条语句。

层次优先遍历和深度优先遍历,论坛中应该有。

原理应该和产品BOM相同。

TA的精华主题

TA的得分主题

 楼主| 发表于 2023-1-17 00:09 | 显示全部楼层
三坛老窖 发表于 2023-1-16 22:43
该题用VBA写,应该没这么复杂,估计不超过50条语句。

层次优先遍历和深度优先遍历,论坛中应该有。

微信截图_20230116234923.png
搜索居然只的一条结果......与BOM用递归历遍还是有差别的,与搜到的案例有点类似,
就如里面说的,倒有点像词语接龙,借鉴21楼 准提部林 老师的附件测试,
(里面数据示例在少了,一时也打不到合适的数据,就在B列再加了一些重复数据测试)
当头字符不一样,但尾字符有相同时,就会出现重复结果,就不知搞不好会不会死循环,
这是重点要排除的,且数据量大时,速度也是个问题

多叉树路径遍历
https://club.excelhome.net/thread-1560693-1-1.html



头像被屏蔽

TA的精华主题

TA的得分主题

发表于 2023-1-17 11:04 | 显示全部楼层
提示: 作者被禁止或删除 内容自动屏蔽

TA的精华主题

TA的得分主题

发表于 2023-1-17 12:18 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
深度优先遍历,动手能力还是不行,看过很多递归搜索的帖子,到自己写就是写不出来

TA的精华主题

TA的得分主题

 楼主| 发表于 2023-1-17 14:54 | 显示全部楼层

微信截图_20230117144943.png

B列数据实际是存在重复的,详见附件及模拟结果:
工作簿1.rar (21.41 KB, 下载次数: 4)

TA的精华主题

TA的得分主题

发表于 2023-1-17 15:51 | 显示全部楼层
头像被屏蔽

TA的精华主题

TA的得分主题

发表于 2023-1-17 16:11 | 显示全部楼层
提示: 作者被禁止或删除 内容自动屏蔽
头像被屏蔽

TA的精华主题

TA的得分主题

发表于 2023-1-17 16:12 | 显示全部楼层
提示: 作者被禁止或删除 内容自动屏蔽

TA的精华主题

TA的得分主题

发表于 2023-1-17 18:01 | 显示全部楼层
参与一下,供参考。。。。。

DFS.7z

27.92 KB, 下载次数: 20

评分

1

查看全部评分

您需要登录后才可以回帖 登录 | 免费注册

本版积分规则

手机版|关于我们|联系我们|ExcelHome

GMT+8, 2024-11-19 20:37 , Processed in 0.036151 second(s), 11 queries , Gzip On, MemCache On.

Powered by Discuz! X3.4

© 1999-2023 Wooffice Inc.

沪公网安备 31011702000001号 沪ICP备11019229号-2

本论坛言论纯属发表者个人意见,任何违反国家相关法律的言论,本站将协助国家相关部门追究发言者责任!     本站特聘法律顾问:李志群律师

快速回复 返回顶部 返回列表