热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

将递归函数非递归化的一般方法(cont)

本文通过模拟汇编里的stack机制,构建一个自己的stack,然后将上一篇blog末尾的递归函数voidbst_walk(bst_node_t*root

本文通过模拟汇编里的stack机制,构建一个自己的stack,然后将上一篇blog末尾的递归函数void bst_walk(bst_node_t *root)非递归化。

o libstack.h

1 #ifndef _LIBSTACK_H
2 #define _LIBSTACK_H
3
4 #ifdef __cplusplus
5 extern "C" {
6 #endif
7
8 typedef void * uintptr_t; /* generic pointer to any struct */
9
10 uintptr_t *stack_init(size_t size);
11 void stack_fini();
12 int stack_isFull();
13 int stack_isEmpty();
14 void push(uintptr_t e);
15 void pop(uintptr_t *e);
16
17 #ifdef __cplusplus
18 }
19 #endif
20
21 #endif /* _LIBSTACK_H */

o libstack.c

1 #include
2 #include
3 #include "libstack.h"
4
5 /**
6 * Basic Stack OPs are supported, including:
7 *
8 * 1. Construct/Destruct a stack
9 * 2. Tell stack is full or empty
10 * 3. push() and pop()
11 *
12 * == DESIGN NOTES ==
13 *
14 * There are 3 static variables reserved,
15 *
16 * ss: stack segment
17 * sp: stack pointer
18 * sz: stack size
19 *
20 * And the stack looks like:
21 *
22 * | RED | ss[-1] ; SHOULD NEVER BE ACCESSED
23 * low-addr &#43;-----&#43; <------------TopOfStack-----------
24 * ^ | | ss[0]
25 * | | | ss[1]
26 * | | ... |
27 * | | |
28 * | | | ss[sz-1]
29 * | &#43;-----&#43; <------------BottomOfStack--------
30 * high-addr | RED | ss[sz] ; SHOULD NEVER BE ACCESSED
31 *
32 * (1) If (sp - ss) &#61;&#61; 0, stack is full
33 * (2) If (sp - ss) &#61;&#61; sz, stack is empty
34 * (3) Push(E): { sp -&#61; 1; *sp &#61; E; }
35 * (4) Pop(&E): { *E &#61; *sp; sp &#43;&#61; 1; }
36 */
37
38 static uintptr_t *ss &#61; NULL; /* stack segment */
39 static uintptr_t *sp &#61; NULL; /* stack pointer */
40 static size_t sz &#61; 0; /* stack size */
41
42 int stack_isFull() { return (sp &#61;&#61; ss); }
43 int stack_isEmpty() { return (sp &#61;&#61; ss &#43; sz); }
44
45 uintptr_t *
46 stack_init(size_t size)
47 {
48 ss &#61; (uintptr_t *)malloc(sizeof (uintptr_t) * size);
49 if (ss &#61;&#61; NULL) {
50 fprintf(stderr, "failed to malloc\n");
51 return NULL;
52 }
53
54 sz &#61; size;
55 sp &#61; ss &#43; size;
56 return ss;
57 }
58
59 void
60 stack_fini()
61 {
62 free(ss);
63 }
64
65 void
66 push(uintptr_t e)
67 {
68 sp -&#61; 1;
69 *sp &#61; e;
70 }
71
72 void
73 pop(uintptr_t *e)
74 {
75 *e &#61; *sp;
76 sp &#43;&#61; 1;
77 }

1. 一旦栈被初始化后&#xff0c;栈指针sp一定是指向栈底&#xff0c;*sp不可访问&#xff08;尤其是写操作&#xff09;&#xff0c;因为不在分配的内存有效范围内&#xff1b;

2. 对于入栈操作(push), 第一步是将sp-&#61;1, 第二步是写入要入栈的元素 (*sp &#61; E); (因为初始化后*sp的内存不可写&#xff0c;所以push操作一定率先改写sp)

3. 对于出栈操作(pop), 顺序与push相反&#xff0c;第一步取出sp指向的内存地址里的内容(E &#61; *sp), 第二步才是将sp&#43;&#61;1;

o foo.c (简单测试)

1 /**
2 * A simple test against stack OPs, including:
3 * o stack_init(), stack_fini()
4 * o stack_isFull(), stack_isEmpty()
5 * o push(), pop()
6 */
7
8 #include
9 #include "libstack.h"
10
11 static void
12 dump_stack(uintptr_t *ss, size_t size)
13 {
14 (void) printf("%p: ", ss);
15 for (int i &#61; 0; i ) {
16 if (ss[i] !&#61; NULL)
17 (void) printf("%-10p ", *(ss&#43;i));
18 else
19 (void) printf("0x%-8x ", 0x0);
20 }
21 printf("\n");
22 }
23
24 int
25 main(int argc, char *argv[])
26 {
27 size_t size &#61; 4;
28
29 uintptr_t *ss &#61; stack_init(size);
30 dump_stack(ss, size);
31
32 for (int i &#61; 0; !stack_isFull(); i&#43;&#43;) {
33 push((uintptr_t)(ss&#43;i));
34 dump_stack(ss, size);
35 }
36
37 (void) printf("\n");
38
39 uintptr_t e &#61; NULL;
40 for (; !stack_isEmpty();) {
41 pop(&e);
42 (void) printf(" (pop) got %-10p\n", e);
43 }
44
45 stack_fini();
46
47 return 0;
48 }

o Makefile

1 CC &#61; gcc
2 CFLAGS &#61; -g -Wall -std&#61;gnu99 -m32
3 INCS &#61;
4
5 TARGET &#61; foo
6
7 all: ${TARGET}
8
9 foo: foo.o libstack.o
10 ${CC} ${CFLAGS} -o $&#64; $^
11
12 foo.o: foo.c
13 ${CC} ${CFLAGS} -c $< ${INCS}
14
15 libstack.o: libstack.c libstack.h
16 ${CC} ${CFLAGS} -c $<
17
18 clean:
19 rm -f *.o
20 clobber: clean
21 rm -f ${TARGET}

o 编译和运行测试

$ make
gcc -g -Wall -std&#61;gnu99 -m32 -c foo.c
gcc -g -Wall -std&#61;gnu99 -m32 -c libstack.c
gcc -g -Wall -std&#61;gnu99 -m32 -o foo foo.o libstack.o$ ./foo
0x8ecc008: 0x0 0x0 0x0 0x0
0x8ecc008: 0x0 0x0 0x0 0x8ecc008
0x8ecc008: 0x0 0x0 0x8ecc00c 0x8ecc008
0x8ecc008: 0x0 0x8ecc010 0x8ecc00c 0x8ecc008
0x8ecc008: 0x8ecc014 0x8ecc010 0x8ecc00c 0x8ecc008(pop) got 0x8ecc014(pop) got 0x8ecc010(pop) got 0x8ecc00c(pop) got 0x8ecc008
$

测试简单且直接&#xff0c;不解释。如果还不确信&#xff0c;可以用gdb调试。


现在对上一篇blog末尾的递归函数使用上面实现的stack进行去递归化改写&#xff0c;改写后的代码如下&#xff1a;

1 void
2 bst_walk(bst_node_t *root)
3 {
4 if (root &#61;&#61; NULL)
5 return;
6
7 (void) stack_init(STACK_SIZE);
8
9 while (root !&#61; NULL || !stack_isEmpty()) {
10 if (root !&#61; NULL) {
11 push((uintptr_t)root);
12 root &#61; root->left;
13 continue;
14 }
15
16 pop((uintptr_t *)(&root));
17 printf("%d\n", root->key);
18
19 root &#61; root->right;
20 }
21
22 stack_fini();
23 }

为方便阅读&#xff0c;下面给出使用meld进行diff后的截图&#xff0c;

  • L7: 构建一个stack, 其中STACK_SIZE是一个宏
  • L22: 将stack销毁
  • L9-14: 首先遍历左子树&#xff0c;不断将结点压入栈中&#xff0c;直到到达最左的叶子结点&#xff0c;那么则执行L16-17 &#xff08;最左的叶子结点也会被压入栈中&#xff09;
  • L16-17: 出栈并打印结点的key
  • L19: 将新的根结点设置为刚刚出栈的结点的右儿子&#xff0c; 重新执行L9-17&#xff0c; 直到所有结点都被遍历到(当然, stack为空)

注&#xff1a; 左图中的函数使用了两次递归&#xff0c;所以将其转化成非递归函数的难度相对较大。

转:https://www.cnblogs.com/idorax/p/6285277.html



推荐阅读
  • 本文介绍了一种算法,用于在一个给定的二叉树中找到一个节点,该节点的子树包含最大数量的值小于该节点的节点。如果存在多个符合条件的节点,可以选择任意一个。 ... [详细]
  • 在使用Hibernate Validator时,遇到HV000030错误提示,这通常与验证注解的选择和使用不当有关。本文将详细介绍@NotNull、@NotEmpty和@NotBlank这三个注解的区别及其正确应用场景。 ... [详细]
  • 本文详细介绍了MySQL表分区的概念、类型及其在实际应用中的实施方法,特别是针对Zabbix数据库的优化策略。 ... [详细]
  • 探讨如何在C++中,当子类实例存储在父类类型的向量中时,正确访问子类特有的成员变量或方法。 ... [详细]
  • 本文探讨了Flutter和Angular这两个流行框架的主要区别,包括它们的设计理念、适用场景及技术实现。 ... [详细]
  • Qt应用开发:创建基本窗口
    本文介绍如何使用Qt框架创建基础窗口的两种方法。第一种方法直接在main函数中创建并显示窗口;第二种方法通过定义一个继承自QWidget的类来实现更复杂的功能。 ... [详细]
  • 美国网络安全:MITRE Shield 积极防御知识库解析
    本文深入解析了MITRE Shield积极防御知识库,探讨其在网络安全领域的应用及意义。 ... [详细]
  • 本文介绍了如何在Spring框架中配置和使用定时任务,包括初始化配置和动态启动定时器的方法。通过示例代码展示了如何利用Spring的TaskScheduler接口来创建和管理定时任务。 ... [详细]
  • 深入解析 Zend Guard 4 功能与配置
    本文旨在通过图文并茂的方式详细介绍 Zend Guard 4 的核心功能及其配置方法,特别适合英文阅读能力有限的技术人员。文中不仅提供了详细的步骤说明,还附带了实际操作中的注意事项,帮助读者更好地理解和应用这一工具。 ... [详细]
  • 本文详细介绍了如何通过配置 Chrome 和 VS Code 来实现对 Vue 项目的高效调试。步骤包括启用 Chrome 的远程调试功能、安装 VS Code 插件以及正确配置 launch.json 文件。 ... [详细]
  • 本文介绍了JSP的基本概念、常用标签及其功能,并通过示例详细说明了如何在JSP页面中使用Java代码。 ... [详细]
  • 题目描述:给定 n 把雨伞和 m 个人,t 分钟后开始下雨。求在每个人只能使用一把雨伞的情况下,最多有多少人可以拿到雨伞。 ... [详细]
  • 【Java数据结构和算法】008栈
    目录0、警醒自己一、栈的应用场景和介绍1、栈的应用场景一个实际的场景:我的思考:2、栈的介绍入栈演示图:出栈演示图 ... [详细]
  • 本文探讨了Codeforces 580C题目——Kefa与公园的问题,深入分析了如何在给定条件下帮助Kefa找到合适的餐厅。 ... [详细]
  • 本书《Pro Git》深入探讨了 Git 版本控制系统的核心概念与高级功能,旨在帮助开发者和团队有效管理代码变更。通过实例和最佳实践,读者将学习如何利用 Git 提升工作效率。 ... [详细]
author-avatar
Era_zhou
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有