zoukankan      html  css  js  c++  java
  • 将递归函数非递归化的一般方法(cont)

    本文通过模拟汇编里的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 <stdio.h>
     2 #include <stdlib.h>
     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  +-----+ <------------TopOfStack-----------
    24  *        ^      |     | ss[0]
    25  *        |      |     | ss[1]
    26  *        |      | ... |
    27  *        |      |     |
    28  *        |      |     | ss[sz-1]
    29  *        |      +-----+ <------------BottomOfStack--------
    30  *     high-addr | RED | ss[sz]  ; SHOULD NEVER BE ACCESSED
    31  *
    32  * (1) If (sp - ss) == 0,  stack is full
    33  * (2) If (sp - ss) == sz, stack is empty
    34  * (3) Push(E): { sp -= 1;   *sp = E; }
    35  * (4) Pop(&E): { *E  = *sp; sp += 1; }
    36  */
    37 
    38 static uintptr_t *ss = NULL; /* stack segment */
    39 static uintptr_t *sp = NULL; /* stack pointer */
    40 static size_t sz = 0;        /* stack size */
    41 
    42 int stack_isFull()  { return (sp == ss); }
    43 int stack_isEmpty() { return (sp == ss + sz); }
    44 
    45 uintptr_t *
    46 stack_init(size_t size)
    47 {
    48     ss = (uintptr_t *)malloc(sizeof (uintptr_t) * size);
    49     if (ss == NULL) {
    50         fprintf(stderr, "failed to malloc
    ");
    51         return NULL;
    52     }
    53 
    54     sz = size;
    55     sp = ss + 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 -= 1;
    69     *sp = e;
    70 }
    71 
    72 void
    73 pop(uintptr_t *e)
    74 {
    75     *e = *sp;
    76     sp += 1;
    77 }

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

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

    3. 对于出栈操作(pop), 顺序与push相反,第一步取出sp指向的内存地址里的内容(E = *sp), 第二步才是将sp+=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 <stdio.h>
     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 = 0; i < size; i++) {
    16         if (ss[i] != NULL)
    17             (void) printf("%-10p ", *(ss+i));
    18         else
    19             (void) printf("0x%-8x ", 0x0);
    20     }
    21     printf("
    ");
    22 }
    23 
    24 int
    25 main(int argc, char *argv[])
    26 {
    27     size_t size = 4;
    28 
    29     uintptr_t *ss = stack_init(size);
    30     dump_stack(ss, size);
    31 
    32     for (int i = 0; !stack_isFull(); i++) {
    33         push((uintptr_t)(ss+i));
    34         dump_stack(ss, size);
    35     }
    36 
    37     (void) printf("
    ");
    38 
    39     uintptr_t e = NULL;
    40     for (; !stack_isEmpty();) {
    41         pop(&e);
    42         (void) printf(" (pop) got %-10p
    ", e);
    43     }
    44 
    45     stack_fini();
    46 
    47     return 0;
    48 }

    o Makefile

     1 CC      = gcc
     2 CFLAGS  = -g -Wall -std=gnu99 -m32
     3 INCS    =
     4 
     5 TARGET  = foo
     6 
     7 all: ${TARGET}
     8 
     9 foo: foo.o libstack.o
    10     ${CC} ${CFLAGS} -o $@ $^
    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=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进行去递归化改写,改写后的代码如下:

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

    为方便阅读,下面给出使用meld进行diff后的截图,

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

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

  • 相关阅读:
    python中的继承和多态
    python中的深浅copy
    面向对象初识
    常用模块,异常处理
    递归,re,time,random
    内置函数,匿名函数
    生成器和迭代器
    记一次nginx由于文件过大的相关问题
    vue的Element+gin实现文件上传
    Vue问题汇总
  • 原文地址:https://www.cnblogs.com/idorax/p/6285277.html
Copyright © 2011-2022 走看看