C基础 stack 设计

前言 - stack 设计思路

  先说说设计 stack 结构的原由. 以前我们再释放查找树的时候多数用递归的后续遍历去释放.

其内部隐含了运行时的函数栈, 有些语言中存在爆栈风险. 所以想运用显示栈来替代隐式函数栈.

这就是我们设计 stack 的背景.  而我们这里的 stack 设计思路也比较直白, 运用可变数组进行尾

部压入和尾部弹出操作. 具体可见下图. 从左到右式弹出过程, 

从右到左就是压入过程. 

 

正文 - stack 详细设计

  话不多说, 先看实现 code 

stack.hhttps://github.com/wangzhione/structc/blob/master/structc/struct/stack.h

#ifndef _STACK_H
#define _STACK_H

#include "struct.h"

#define INT_STACK   (1 << 8)

// 
// struct stack 对象栈
// stack empty <=> tail = -1 
// stack full  <=> tail == cap
//
struct stack {
    int      tail;  // 尾结点
    int       cap;  // 栈容量
    void **  data;  // 栈实体
};

//
// stack_init - 初始化 stack 对象栈
// stack_free - 清除掉 stack 对象栈
// return   : void
//
inline void stack_init(struct stack * s) {
    assert(s && INT_STACK > 0);
    s->tail = -1;
    s->cap  = INT_STACK;
    s->data = malloc(sizeof(void *) * INT_STACK);
}

inline void stack_free(struct stack * s) {
    free(s->data);
}

//
// stack_delete - 删除 stack 对象栈
// s        : stack 对象栈
// fdie     : node_f push 结点删除行为
// return   : void
//
inline void stack_delete(struct stack * s, node_f fdie) {
    if (s) {
        if (fdie) {
            while (s->tail >= 0)
                fdie(s->data[s->tail--]);
        }
        stack_free(s);
    }
}

//
// stack_empty - 判断 stack 对象栈是否 empty
// s        : stack 对象栈
// return   : true 表示 empty
//
inline bool stack_empty(struct stack * s) {
    return s->tail <  0;
}

//
// stack_top - 获取 stack 栈顶对象
// s        : stack 对象栈
// return   : 栈顶对象
//
inline void * stack_top(struct stack * s) {
    return s->tail >= 0 ? s->data[s->tail] : NULL;
}

//
// stack_pop - 弹出栈顶元素
// s        : stack 对象栈
// return   : void
//
inline stack_pop(struct stack * s) {
    if (s->tail >= 0) --s->tail;
}

//
// stack_push - 压入元素到对象栈栈顶
// s        : stack 对象栈
// m        : 待压入的对象
// return   : void
// 
inline void stack_push(struct stack * s, void * m) {
    if (s->cap <= s->tail) {
        s->cap <<= 1;
        s->data = realloc(s->data, sizeof(void *) * s->cap);
    }
    s->data[++s->tail] = m;
}

#endif//_STACK_H

INT_STACK 是拍脑门搞得 8 x 8, 唯一的损耗点可能在 stack_top 和 stack_empty 配合的时候, 需要

冗余判断一步 tail >= 0. 不过随着条件的分支预测, 实际影响不大, 也还好. 我们不妨写个业务测试. 

#include <stack.h>

void stack_test(void) {
    struct stack s; stack_init(&s);

    char * str = NULL;
    stack_push(&s, ++str);
    stack_push(&s, ++str);
    stack_push(&s, ++str);

    // 数据输出
    for (char * now; (now = stack_top(&s)); stack_pop(&s))
        printf("now = %p\n", now);

    stack_push(&s, ++str);
    stack_push(&s, ++str);

    for (char * now; (now = stack_top(&s)); stack_pop(&s))
        printf("now = %p\n", now);

    stack_free(&s);
}

那最终看 stack 实际运用场景吧, 运用显示栈来销毁查找树

// rtree_die - 后序删除树结点
static void rtree_die(struct $rtree * root, node_f fdie) {
    struct $rtree * pre = NULL;
    struct stack s; stack_init(&s);
    stack_push(&s, root);
    do {
        struct $rtree * cur = stack_top(&s);
        if ((!cur->left && !cur->right) 
         || ((cur->left == pre || cur->right == pre) && pre)) {
            fdie(pre = cur);
            stack_pop(&s);
        } else {
            if (cur->right)
                stack_push(&s, cur->right);
            if (cur->left)
                stack_push(&s, cur->left);
        }
    } while (!stack_empty(&s));
    stack_free(&s);
}

更多细节代码可以阅读 rtree.h 对于二叉树后续非递归遍历, 压入右子树, 左子树,对比上次弹出的结点 ...

 

后记 - stack 未来展望

  Friend https://music.163.com/#/song?id=523560 

  错误是难免的, 欢迎交流提升 ~ 

  基于当前 stack 设计, 未来展望具体从两方面处理. 复杂方面, 可以优化一下内存相关操作, 初始化,

扩容, 缩容等. 简单方面, 大家也看出来了, 这个栈代码极其少, 纯追求性能都可以直接放弃封装, 内嵌到

需要使用的地方和大业务混为一体. 那今天就到这里了, 2019/08/25 21:50 Dota2 OG 王朝真强👍 

 

posted on 2019-08-25 22:02  喜欢兰花山丘  阅读(388)  评论(0编辑  收藏  举报