C基础 stack 设计
前言 - stack 设计思路
先说说设计 stack 结构的原由. 以前我们再释放查找树的时候多数用递归的后续遍历去释放.
其内部隐含了运行时的函数栈, 有些语言中存在爆栈风险. 所以想运用显示栈来替代隐式函数栈.
这就是我们设计 stack 的背景. 而我们这里的 stack 设计思路也比较直白, 运用可变数组进行尾
部压入和尾部弹出操作. 具体可见下图. 从左到右式弹出过程,
从右到左就是压入过程.
正文 - stack 详细设计
话不多说, 先看实现 code
stack.h - https://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 王朝真强👍