将递归函数非递归化的一般方法(cont)
本文通过模拟汇编里的stack机制,构建一个自己的stack,然后将上一篇blog末尾的递归函数void bst_walk(bst_node_t *root)非递归化。
o libstack.h
#ifndef _LIBSTACK_H
#define _LIBSTACK_H #ifdef __cplusplus
extern "C" {
#endif typedef void * uintptr_t; /* generic pointer to any struct */ uintptr_t *stack_init(size_t size);
void stack_fini();
int stack_isFull();
int stack_isEmpty();
void push(uintptr_t e);
void pop(uintptr_t *e); #ifdef __cplusplus
}
#endif #endif /* _LIBSTACK_H */
o libstack.c
#include <stdio.h>
#include <stdlib.h>
#include "libstack.h" /**
* Basic Stack OPs are supported, including:
*
* 1. Construct/Destruct a stack
* 2. Tell stack is full or empty
* 3. push() and pop()
*
* == DESIGN NOTES ==
*
* There are 3 static variables reserved,
*
* ss: stack segment
* sp: stack pointer
* sz: stack size
*
* And the stack looks like:
*
* | RED | ss[-1] ; SHOULD NEVER BE ACCESSED
* low-addr +-----+ <------------TopOfStack-----------
* ^ | | ss[0]
* | | | ss[1]
* | | ... |
* | | |
* | | | ss[sz-1]
* | +-----+ <------------BottomOfStack--------
* high-addr | RED | ss[sz] ; SHOULD NEVER BE ACCESSED
*
* (1) If (sp - ss) == 0, stack is full
* (2) If (sp - ss) == sz, stack is empty
* (3) Push(E): { sp -= 1; *sp = E; }
* (4) Pop(&E): { *E = *sp; sp += 1; }
*/ static uintptr_t *ss = NULL; /* stack segment */
static uintptr_t *sp = NULL; /* stack pointer */
static size_t sz = ; /* stack size */ int stack_isFull() { return (sp == ss); }
int stack_isEmpty() { return (sp == ss + sz); } uintptr_t *
stack_init(size_t size)
{
ss = (uintptr_t *)malloc(sizeof (uintptr_t) * size);
if (ss == NULL) {
fprintf(stderr, "failed to malloc\n");
return NULL;
} sz = size;
sp = ss + size;
return ss;
} void
stack_fini()
{
free(ss);
} void
push(uintptr_t e)
{
sp -= ;
*sp = e;
} void
pop(uintptr_t *e)
{
*e = *sp;
sp += ;
}
1. 一旦栈被初始化后,栈指针sp一定是指向栈底,*sp不可访问(尤其是写操作),因为不在分配的内存有效范围内;
2. 对于入栈操作(push), 第一步是将sp-=1, 第二步是写入要入栈的元素 (*sp = E); (因为初始化后*sp的内存不可写,所以push操作一定率先改写sp)
3. 对于出栈操作(pop), 顺序与push相反,第一步取出sp指向的内存地址里的内容(E = *sp), 第二步才是将sp+=1;
o foo.c (简单测试)
/**
* A simple test against stack OPs, including:
* o stack_init(), stack_fini()
* o stack_isFull(), stack_isEmpty()
* o push(), pop()
*/ #include <stdio.h>
#include "libstack.h" static void
dump_stack(uintptr_t *ss, size_t size)
{
(void) printf("%p: ", ss);
for (int i = ; i < size; i++) {
if (ss[i] != NULL)
(void) printf("%-10p ", *(ss+i));
else
(void) printf("0x%-8x ", 0x0);
}
printf("\n");
} int
main(int argc, char *argv[])
{
size_t size = ; uintptr_t *ss = stack_init(size);
dump_stack(ss, size); for (int i = ; !stack_isFull(); i++) {
push((uintptr_t)(ss+i));
dump_stack(ss, size);
} (void) printf("\n"); uintptr_t e = NULL;
for (; !stack_isEmpty();) {
pop(&e);
(void) printf(" (pop) got %-10p\n", e);
} stack_fini(); return ;
}
o Makefile
CC = gcc
CFLAGS = -g -Wall -std=gnu99 -m32
INCS = TARGET = foo all: ${TARGET} foo: foo.o libstack.o
${CC} ${CFLAGS} -o $@ $^ foo.o: foo.c
${CC} ${CFLAGS} -c $< ${INCS} libstack.o: libstack.c libstack.h
${CC} ${CFLAGS} -c $< clean:
rm -f *.o
clobber: clean
rm -f ${TARGET}
o 编译和运行测试
$ make
gcc -g -Wall -std=gnu99 -m32 -c foo.c
gcc -g -Wall -std=gnu99 -m32 -c libstack.c
gcc -g -Wall -std=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
$
测试简单且直接,不解释。如果还不确信,可以用gdb调试。
现在对上一篇blog末尾的递归函数使用上面实现的stack进行去递归化改写,改写后的代码如下:
void
bst_walk(bst_node_t *root)
{
if (root == NULL)
return; (void) stack_init(STACK_SIZE); while (root != NULL || !stack_isEmpty()) {
if (root != NULL) {
push((uintptr_t)root);
root = root->left;
continue;
} pop((uintptr_t *)(&root));
printf("%d\n", root->key); root = root->right;
} stack_fini();
}
为方便阅读,下面给出使用meld进行diff后的截图,

- L7: 构建一个stack, 其中STACK_SIZE是一个宏
- L22: 将stack销毁
- L9-14: 首先遍历左子树,不断将结点压入栈中,直到到达最左的叶子结点,那么则执行L16-17 (最左的叶子结点也会被压入栈中)
- L16-17: 出栈并打印结点的key
- L19: 将新的根结点设置为刚刚出栈的结点的右儿子, 重新执行L9-17, 直到所有结点都被遍历到(当然, stack为空)
注: 左图中的函数使用了两次递归,所以将其转化成非递归函数的难度相对较大。
将递归函数非递归化的一般方法(cont)的更多相关文章
- 在二叉搜索树(BST)中查找第K个大的结点之非递归实现
一个被广泛使用的面试题: 给定一个二叉搜索树,请找出其中的第K个大的结点. PS:我第一次在面试的时候被问到这个问题而且让我直接在白纸上写的时候,直接蒙圈了,因为没有刷题准备,所以就会有伤害.(面完的 ...
- 二叉树系列 - [LeetCode] Symmetric Tree 判断二叉树是否对称,递归和非递归实现
Given a binary tree, check whether it is a mirror of itself (ie, symmetric around its center). For e ...
- Java遍历文件夹的两种方法(非递归和递归)
import java.io.File; import java.util.LinkedList; public class FileSystem { public static int num ...
- 二叉树中序遍历,先序遍历,后序遍历(递归栈,非递归栈,Morris Traversal)
例题 中序遍历94. Binary Tree Inorder Traversal 先序遍历144. Binary Tree Preorder Traversal 后序遍历145. Binary Tre ...
- 字符串拷贝函数递归与非递归的C语言实现
初学递归的时候,觉得很抽象,不好分析,确实如此,尤其是有些时候控制语句不对,导致程序进去无限次的调用,更严重的是栈溢出.既要正确的控制结束语句,又要有正确的进入下次递归的语句,还要有些操作语句.... ...
- 算法笔记_013:汉诺塔问题(Java递归法和非递归法)
目录 1 问题描述 2 解决方案 2.1 递归法 2.2 非递归法 1 问题描述 Simulate the movement of the Towers of Hanoi Puzzle; Bonus ...
- 非递归创建二叉树( C++队列 )
非递归按照 层序 创建二叉树,利用 队列(即可先进先出特点)存放已访问的结点元素的地址. 初始化:front=rear= -1: 每储存一个结点元素 rear+1 ,利用 rear%2==0 来使 f ...
- 排序算法练习--JAVA(插入、直接选择、冒泡、快速排序、非递归快速排序)
排序算法是数据结构中的经典算法知识点,也是笔试面试中经常考察的问题,平常学的不扎实笔试时候容易出洋相,回来恶补,尤其是碰到递归很可能被问到怎么用非递归实现... package sort; impor ...
- C语言实现 二分查找数组中的Key值(递归和非递归)
基本问题:使用二分查找的方式,对数组内的值进行匹配,如果成功,返回其下标,否则返回 -1.请使用递归和非递归两种方法说明. 非递归代码如下: #include <stdio.h> int ...
随机推荐
- ExtJs之表格控件入门
extjs的表格功能非常强大,包括了排序,缓存,拖动,隐藏某一列,自动显示行号,列汇总,单元格编辑等实用功能.表格由类Ext.grid.GridPanel定义,继承自Panel,其xtype为grid ...
- Docker 01 Introduction
Docker的组成: Docker Engine,一个轻量级.强大的开源容器虚拟化平台,使用包含了工作流的虚拟化技术,帮助用户建立.并容器化一个应用. Docker Hub,提供的一个SaaS服务,用 ...
- Js模块模式
模块模式 索引 引子 什么是模块模式 命名空间模式 声明依赖 私有和特权成员 即时函数 揭示模块模式 结语 引子 这篇算是对第9篇中内容的发散和补充,当时我只是把模块模式中的一些内容简单的归为函数篇中 ...
- soket.io.js + angular.js + express.js(node.js)
soket.io.js + angular.js + express.js(node.js) 今天搭建个soket.io.js + angular.js + express.js的环境, 采坑无数,特 ...
- 图解Javascript之字符串
好东西分享给大家,但要尊重事实!!!因此特别说明:本图非我本人亲自所作,乃我大天朝网友所绘制.个人感觉此图,覆盖全面,细节考虑甚周全,因此分享给大家,同时在此特别感谢网友的无私分享!
- 企业架构研究总结(25)——TOGAF架构开发方法(ADM)之迁移规划阶段
1.8 迁移规划(Migration Planning) 企业架构开发方法各阶段——迁移规划 1.8.1 目标 本阶段的目标是: 确保实施和迁移规划与企业中各种管理框架相协调. 通过对每个进行中的成本 ...
- 利用HttpWebRequest和HttpWebResponse获取Cookie并实现模拟登录
利用HttpWebRequest和HttpWebResponse获取Cookie并实现模拟登录 tring cookie = response.Headers.Get("Set-Cookie ...
- JAVA-读取文件部分内容计算HASH值
对于一些大文件,有时会需要计算部分内容的Hash,下面的函数计算了 文件头尾各1M,中间跳跃100M取10K 以及文件大小的Hash值 public static String CalHash(Str ...
- 自己动手写spring容器(3)
好久没有写博客了,今天闲下来将之前未完成的表达出来. 在之前的文章自己动手写spring容器(2)中完成了对spring的依赖注入的实现,这篇将会介绍spring基于注解的依赖注入的实现. 在一般的J ...
- [Android学习笔记4]四大应用组件之一:Service 上
一.什么是Service 一个Service就是一个能够在后台执行长时操作的应用程序组件,并且不提供用户界面.一个应用程序组件能够启动一个Service,即使用户切换到另一个应用程序,这个Servic ...