偶尔敲代码,今天看树的遍历方式递归和非递归方式实现,碰到了一个关于栈的问题。
栈
栈的定义:栈是限定仅在表头进行插入和删除操作的线性表。要搞清楚这个概念,首先要明白”栈“原来的意思,如此才能把握本质。"栈“者,存储货物或供旅客住宿的地方,可引申为仓库、中转站,所以引入到计算机领域里,就是指数据暂时存储的地方,所以才有进栈、出栈的说法。(来源于百度百科)。
栈是一种Last In First Out (First In Last Out)的形式。总的来说,栈主要有两大函数:Pop()(入栈)和Push()(出栈),这里就用我今天所使用的代码示例:
Pop():① 将元素从栈顶压入,② top指针自增
//进栈
Status Push(Stack *s, BiTree c)
{
//如果空间不够,增加空间的分配
if (s->top - s->base >= s->stacksize)
{
s->base = (BiTree*)realloc(s->base, sizeof(BiTree)*(s->stacksize + STACKINCREMENT));
s->stacksize = s->stacksize + STACKINCREMENT;
}
*(s->top++) = c;
return OK;
}
Push():①干掉top处的元素,②top指针自减。
//出栈
Status Pop(Stack *s, BiTree *c)
{
if (StackEmpty(s))
return ERROR;
*c = *(--s->top); //减一
return OK;
}
栈主要两大函数实现类似于这样,基本思想都一样:
递归与非递归
非递归效率高;递归代码写出来思路清晰,可读性强。
递归好处:代码更简洁清晰,可读性更好
众所周知,非递归的可读性很好,一直往下看代码就知道要干啥了(初学者都比较喜欢这种)。但是,递归的可读性更好,而且代码简洁,后期对于整个代码的维护也是很方便快捷。有人肯定就会问,递归看得我头晕,看着看着就不知道到哪了(其实这只是你的编程能力没有多加练习,这都不是事)。
递归坏处:由于递归需要系统堆栈,所以空间消耗要比非递归代码要大很多。而且,如果递归深度太大,可能系统撑不住。
说实话,递归真的是方便了程序员而为难了机器,它只要得到数学公式就能很方便的写出程序。优点就是易理解,容易编程。但递归是用栈机制实现的,每深入一层,都要占去一块栈数据区域,对嵌套层数深的一些算法,递归会力不从心,空间上会以内存崩溃而告终,而且递归也带来了大量的函数调用,这也有许多额外的时间开销。
递归简介
一个递归算法必须有两个部分:初始情况和递归部分。初始情况只处理可以直接解决而不需要再次递归调用的简单输入。递归部分包含对算法的一次或者多次递归调用。
递归算法的实际总能使用下面的方法实现:首先写出初始情况,然后考虑通过一个或多个较小但是类似子问题的结果来解决原问题。
递归成功的秘诀在于:不要担心递归方法是如何解决子问题的。只要简单地接收它能正确地解决子问题,而且使用子问题的求解结果就能正确地解决原问题。只要在不超出递归调用的范围内分析递归过程,子问题就会迎刃而解。
递归调用子程序会生成一个活动记录,虽然递归方法实现起来比较容易,而且清晰易懂,但是有些时候我们希望避免因递归函数调用而产生的庞大的时空代价。
在某些情况下,递归可以轻易用迭代来代替(如计算阶乘),但是当实现有多个分支的算法时,就很难用迭代来代替递归,所以必须使用递归或者与递归等价的算法。值得庆幸的是,我们可以使用栈来方便的模拟递归,改写递归程序。
递归和栈有什么联系?
举个例子:
假设如下问题的依赖关系:
【A】----依赖---->【B】----依赖---->【C】
我们的终极目的是要解决问题A,
那么三个问题的处理顺序如下:
开始处理问题A;
由于A依赖B,因此开始处理问题B;
由于B依赖C,开始处理问题C;
结束处理问题C;
结束处理问题B;
结束处理问题A。
从上面的例子可以看出来,这是一个先进后出的形式,和栈的形式一模一样。
引入一个递归的例子(Hanoi Tower)
在树的遍历问题中,递归利用它先进后出的特点不使用栈,而对于非递归想要实现树的遍历就要借用栈来实现。
树的先序,中序,后序遍历(递归):
基础的结构体:
typedef int Status;
#define OK 1
#define ERROR 0
#define OVERFLOW -2
//二叉树的二叉链表存储表示
typedef char TElemType;
typedef struct BiNode
{
TElemType data; //存放数据
struct BiNode *lchild, *rchild; //左右孩子
} BiNode, *BiTree;
创建树:
Status CreatBiTree(BiTree *T)
{
char ch;
scanf_s("%c", &ch);
//如果当前输入的字符为空格,则(*T)指向空树。
if (ch == ' ')
{
(*T) = NULL;
}
else
{
if (!((*T) = (BiTree)malloc(sizeof(BiNode))))
exit(OVERFLOW);
(*T)->data = ch; //生成根结点
CreatBiTree(&((*T)->lchild)); //构造左子树
CreatBiTree(&((*T)->rchild)); //构造右子树
}
return OK;
}
先序:
/*
* 采用二叉链表存储结构,Visit是对数据元素操作的应用函数,
* 先序遍历二叉树T的递归算法,对每个数据元素调用函数Visit。
*/
Status XianXuTraverse_Recursive(BiTree T, Status(*Visit)(TElemType e))
{
if (T)
{
if (Visit(T->data))
if (XianXuTraverse_Recursive(T->lchild, Visit))
if (XianXuTraverse_Recursive(T->rchild, Visit))
return OK;
}
else
return OK; //当T为空树时,停止递归。
}
中序:
Status ZhongXuTraverse_Recursive(BiTree T, Status(*Visit)(TElemType e))
{
if (T)
{
if (ZhongXuTraverse_Recursive(T->lchild, Visit))
if (Visit(T->data))
if (ZhongXuTraverse_Recursive(T->rchild, Visit))
return OK;
}
else
return OK;
}
后序:
Status HouXuTraverse_Recursive(BiTree T, Status(*Visit)(TElemType e))
{
if (T)
{
if (HouXuTraverse_Recursive(T->lchild, Visit))
if (HouXuTraverse_Recursive(T->rchild, Visit))
if (Visit(T->data))
return OK;
}
else
return OK;
}
主函数实现:
//遍历数据元素时所调用函数
Status Print(TElemType e)
{
putchar(e);
return OK;
}
int main()
{
BiTree T;
CreatBiTree(&T);
//先序
printf("递归算法(先序遍历):");
XianXuTraverse_Recursive(T, Print); putchar('\n');
//中序
printf("递归算法(中序遍历):");
ZhongXuTraverse_Recursive(T, Print); putchar('\n');
//后序
printf("递归算法(后序遍历):");
HouXuTraverse_Recursive(T, Print); putchar('\n');
return 0;
}
根据书上例子:ABC##DE#G##F###(#代表空格)
运行结果:
<?php
while(true) {
echo "不想学习!!!";
}
?>