将递归函数非递归化的一般方法(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 ...
随机推荐
- C#事件与委托的区别
C#事件与委托的区别 1. 委托 事件是利用委托来定义的,因此先解释委托.委托是一个类,它与其他类如int,string等没有本质区别,int代表的是所有的整形,而string代表的是字符串,委托则代 ...
- MFC双缓冲和裁剪问题导致闪烁
问题描述: 应用场景:在对话框中,自定义一个MFC图形控件(为了描述方便,暂定为HSPaintControl),控件覆盖整个对话框的客户区,属于最底层的控件,在这之上放置了很多其他的小图形控件. 问题 ...
- XHTML
XHTML 是 HTML 与 XML(扩展标记语言)的结合物. XHTML 包含了所有与 XML 语法结合的 HTML 4.01 元素. 最主要的不同: XHTML 元素必须被正确地嵌套. XHTML ...
- iOS基础 - 单元测试
单元测试(unit testing):对软件中最小可测试单元进行检查和验证.一般面向过程的语言中,基本单元为函数,面向对象的语言中,基本单元通常是类,其实对于一个手机上的app来说基本单元也可以是一个 ...
- SQLSERVER误删Windows登录用户
SQLSERVER误删除了Windows登录用户验证方式使用Windows身份验证的解决方法 SQLSERVER误删Windows登录用户验证方式使用Windows身份验证的解决方法 今天看到这篇 ...
- 使用celery之怎么让celery跑起来
celery 官网帮助文档 http://docs.celeryproject.org/en/latest/index.html 前言 自从发了上次的文章使用celery之深入celery配置, 有 ...
- C#获取友好时间差
/// <summary> /// 获取时间差 /// </summary> /// <param name="dtOld">要减的时间< ...
- Mac 下卸载 Graphviz
打算安装这个程序,但是听说这个软件在 Mac 上有问题,所以先记录下卸载方法. 方法一: 双击 pkg 文件后,当看到安装器界面时: 按 Command + i 打开安装包的信息窗口: 展开后可以看到 ...
- 使用 Aspose.Cells 实现 excel导入
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.We ...
- Object-c学习之路二(oc内存管理黄金法则1)
今天看了看oc的内存管理,自己管理内存不能随便的release和retain 法则会出现野指针等错误.下面以人和读书的例子做练习. 1.主函数 // // main.m // MemoryManage ...