将递归函数非递归化的一般方法(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 ...
随机推荐
- javascript设计模式系列
javascript设计模式系列 创建型: 1.抽象工厂模式(Abstract Factory) 2.构建者模式(Builder) 3.工厂方法模式(Factory Method) 4.原型模式( ...
- iOS基础 - Modal展示控制器
一.利用Modal形式展示控制器 1.如何展示 // vc就是要展示的新控制器 [self presentViewController:vc animated:YES completion:^{ NS ...
- C++中内存泄露的检测
C++没有java的内存垃圾回收机制,在程序短的时候可能比较容易发现问题,在程序长的时候是否有什么检测的方法呢? 假设有一个函数可以某点检测程序的内存使用情况,那是否可以在程序开始的时候设置一个点,在 ...
- C#socket通信1
.net平台下C#socket通信(上) 完全是基础,新手可以随意看看,大牛可以关闭浏览页了,哈哈. 在开始介绍socket前先补充补充基础知识,在此基础上理解网络通信才会顺理成章,当然有基础的可以跳 ...
- [原]使用MachOView辅助破解AppStore应用
在破解iOS应用的过程中,需要经常使用 otool 开获取程序本身的信息(比如:是否启用了PIE),获取加密信息, 但是CLI的程序在直观性上还是不如GUI的, 下面描述使用MachOView来查看到 ...
- 【C基础】const用法
1.const 和 define 异同 同:const 和 define都是修饰常量 异:const修饰的常量只是编译器的一种优化,它是可以通过内存地址修改const修饰的常量:而define修饰的常 ...
- cocos2d-x 实现跨平台的目录遍历
可能各位看官们有更好的方法,请不吝赐教. #ifdef _WIN32 #include <io.h> #else #include <unistd.h> #include &l ...
- HTML5-WebSocket-初探
1.环境准备 主要是用<HTML5 程序设计>(第二版)作为学习参考资料.但是上面用的WebSocket服务器是用python写的.偶不懂python,于是得找另外一个替代实现,这里适用n ...
- TFTP:简单文本传输协议,BOOTP:引导程序协议
TFTP:简单文本传输协议,BOOTP:引导程序协议 1.TFTP: TFTP(Trivial File Transfer Protocol,简单文件传输协议)是TCP/IP协议族中的一个用 ...
- javac命令
javac命令 javac命令初窥 注:以下红色标记的参数在下文中有所讲解. 用法: javac <options> <source files> 其中, 可能的选项包括: ...