本文通过模拟汇编里的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)的更多相关文章

  1. 在二叉搜索树(BST)中查找第K个大的结点之非递归实现

    一个被广泛使用的面试题: 给定一个二叉搜索树,请找出其中的第K个大的结点. PS:我第一次在面试的时候被问到这个问题而且让我直接在白纸上写的时候,直接蒙圈了,因为没有刷题准备,所以就会有伤害.(面完的 ...

  2. 二叉树系列 - [LeetCode] Symmetric Tree 判断二叉树是否对称,递归和非递归实现

    Given a binary tree, check whether it is a mirror of itself (ie, symmetric around its center). For e ...

  3. Java遍历文件夹的两种方法(非递归和递归)

    import java.io.File; import java.util.LinkedList; public class FileSystem {    public static int num ...

  4. 二叉树中序遍历,先序遍历,后序遍历(递归栈,非递归栈,Morris Traversal)

    例题 中序遍历94. Binary Tree Inorder Traversal 先序遍历144. Binary Tree Preorder Traversal 后序遍历145. Binary Tre ...

  5. 字符串拷贝函数递归与非递归的C语言实现

    初学递归的时候,觉得很抽象,不好分析,确实如此,尤其是有些时候控制语句不对,导致程序进去无限次的调用,更严重的是栈溢出.既要正确的控制结束语句,又要有正确的进入下次递归的语句,还要有些操作语句.... ...

  6. 算法笔记_013:汉诺塔问题(Java递归法和非递归法)

    目录 1 问题描述 2 解决方案  2.1 递归法 2.2 非递归法 1 问题描述 Simulate the movement of the Towers of Hanoi Puzzle; Bonus ...

  7. 非递归创建二叉树( C++队列 )

    非递归按照 层序 创建二叉树,利用 队列(即可先进先出特点)存放已访问的结点元素的地址. 初始化:front=rear= -1: 每储存一个结点元素 rear+1 ,利用 rear%2==0 来使 f ...

  8. 排序算法练习--JAVA(插入、直接选择、冒泡、快速排序、非递归快速排序)

    排序算法是数据结构中的经典算法知识点,也是笔试面试中经常考察的问题,平常学的不扎实笔试时候容易出洋相,回来恶补,尤其是碰到递归很可能被问到怎么用非递归实现... package sort; impor ...

  9. C语言实现 二分查找数组中的Key值(递归和非递归)

    基本问题:使用二分查找的方式,对数组内的值进行匹配,如果成功,返回其下标,否则返回 -1.请使用递归和非递归两种方法说明. 非递归代码如下: #include <stdio.h> int ...

随机推荐

  1. EF Power Tools 参数错误 HRESULT:0x80070057 (E_INVALIDARG))

    数据库名称使用数字开头,使用EF Power Tools生成映射时,会提示:“参数错误. (Exception from HRESULT: 0x80070057 (E_INVALIDARG))”. 解 ...

  2. C/C++基础知识总结——数组、指针域、字符串

    1. 数组 1.1 数组作为函数参数 (1) 如果使用数组作为函数的参数,则实参和形参都是数组名,且类型要相同.数组名做参数时传递的是地址 (2) 使用方法: void rowSum(int a[][ ...

  3. url在线编码和解码

    在工作中,经常遇到encode之后的url.想查看里面的某个参数的时候,很不直观.今天在网上搜了一下对url在线编码和解码的网站.对我来说,使用起来很方便.而且这个网站里面,不仅仅有对url的编码和解 ...

  4. Please Send Me a Card

    Please Send Me a Card 发现身边很多程序员都能看懂英文技术文章的60%-80%内容,但大家都有一个毛病,就是不会说,不会写作,在逛英文技术社区的时候,想发表点什么评论,总担心自己写 ...

  5. HDU 4618 - Palindrome Sub-Array(2013MUTC2-1008)(DP)

    d(i,j,k)表示左上角坐标为(i,j),k为正方形边长 d(i,j,k)=1,如果d(i+1,j+1,k-2)=0,且上下两个外围的相等且回文,左右两个外围的相等且回文:否则d(i,j,k)=0 ...

  6. 关于PDF.NET开发框架对Mysql Sqlite PostgreSQL数据库分页支持的个人看法

    关于PDF.NET开发框架的名字由来  在设计www.pwmis.com站点的时候,考虑到架构的兼容性和将来升级的可能性,最重要的是没有足够的时间去为网站添加和维护很多复杂的程序,所以在借鉴前人成功经 ...

  7. 搜索广告与广告网络Demand技术-流式计算平台

    流式计算平台-Storm 我们以Storm为例来看流式计算的功能是什么. 下面内容引用自大圆的博客.在Storm中,一个实时应用的计算任务被打包作为Topology发布,这同Hadoop的MapRed ...

  8. JPA 批注参考

    body, p, th, td, li, ul, ol, h1, h2, h3, h4, h5, h6, pre { font-family: simsun; line-height: 1.4; } ...

  9. ngx-push-stream模块源码学习(一)——序言

    一.概述     与传统的request-response的web应用模式不同,comet是一种长连接(long-held)的应用模式,从而允许服务端主动向客户端推送数据.     主流的comet技 ...

  10. ASP.NET MVC:多语言的三种技术处理策略

    ASP.NET MVC:多语言的三种技术处理策略 背景 本文介绍了多语言的三种技术处理策略,每种策略对应一种场景,这三种场景是: 多语言资源信息只被.NET使用. 多语言资源信息只被Javascrip ...