前言 - 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 王朝真强👍