最近在给学生讲授数据结构课程的树和二叉树这一章时,调试书的的代码发现几个问题,花了不少时间才解决,感觉自己对指针这玩意的理解还有待深入。
二叉树的遍历有三种,先序、中序和后序,通常是利用递归来进行遍历。如果不利用递归,这时候就要借鉴于堆栈(Stack)这种数据结构。根据树上的定义,我们可以定义以下栈来存储树的节点:
- //二叉树节点的定义
- typedef struct BiNode {
- TElemType data;
- struct BiNode *lchild, *rchild; //左右孩子指针
- }BiTNode, *BiTree;
- #define STACK_INIT_SIZE 100 //存储空间初始分配量
- #define STACKINCREMENT 10 //存储空间分配增量
- typedef struct {
- BiTNode* base; //在栈构造之前和销毁后,该指针为NULL
- BiTNode* top; //栈顶指针
- int stacksize; //当前已经分配的存储空间,以元素为单位
- }SqStack;
这个地方的关键在于,平时我们入栈的可能是build-in type数据,如int。而这个地方我们入栈的是树的节点。其关键的Push和Pop操作,按照书上的实现如下:
- Status Push(SqStack &S,BiTNode* e)
- {
- if(S.top - S.base >= S.stacksize) //栈满,追加存储空间
- {
- S.base = (BiTNode*)realloc(S.base, (S.stacksize + STACKINCREMENT)*sizeof /
- (BiTNode));
- if(!S.base) exit(OVERFLOW);
- S.top = S.base + S.stacksize;
- S.stacksize += STACKINCREMENT;
- }
- if(e != NULL)
- {
- *S.top = *e; //e的值(*e)入栈
- S.top++;
- }
- return OK;
- } //Push
- /*
- * 说明:该函数通过参数e将栈顶元素返回
- * 但此时的e传递进来时,必须已经赋值
- */
- Status Pop(SqStack &S, BiTNode* e)
- {
- if(S.base == S.top || !e)
- {
- return ERROR;
- }
- --S.top;
- (*e) = *S.top; //弹出的值赋予给*e
- //e = S.top; //error.
- return OK;
- }
在上面代码中,要注意的是,我们入栈的是节点包含的数据,而不是节点指针本身。在Push实现时,我就犯了以上错误,代码如下:
- S.top = e; //error!
- S.top++;
结果把栈顶指针给改变了。
但是上述的Pop函数在实际调用过程中也存在着很大问题,见下面代码:
- //中序非递归遍历二叉树T,对每个结点调用函数Visit一次且仅一次。
- Status InOrderTraverse1(BiTree T, Status(*Visit)(TElemType e)) //方法1(6.2)
- {
- SqStack S;
- InitStack(S);
- BiTNode* p = T;
- while(p != NULL || !IsStackEmpty(S))
- {
- while(p != NULL)
- {
- Push(S, p);
- p = p->lchild;
- }
- if(!IsStackEmpty(S))
- {
- p = (BiTNode*)malloc(sizeof(BiTNode));
- Pop(S, p); //Here, the problem is how to delete p?
- if(!Visit(p->data))
- return ERROR;
- p = p->rchild;
- }
- }
- DestroyStack(S);
- return OK;
- }
见上面我的注释//Here, the problem is how to delete p? 在从栈顶取元素时,我们必须传入指针p,但是此p必须在使用前赋值,否则出错!关键是在何时delete p。想了很久,一直没有找到比较好的解决办法???
最终,采用的是另一种方法,即Pop函数实现如下:
- /*
* 说明:该函数通过函数返回值将栈顶元素返回
*
*/
BiTNode* Pop(SqStack &S)
{
if(S.base == S.top)
{
return NULL;
}
--S.top;
return S.top;
}
最终,在遍历函数中调用过程如下:
- //中序非递归遍历二叉树T,对每个结点调用函数Visit一次且仅一次。
- Status InOrderTraverse1(BiTree T, Status(*Visit)(TElemType e)) //方法1(6.2)
- {
- SqStack S;
- InitStack(S);
- BiTNode* p = T;
- while(p != NULL || !IsStackEmpty(S))
- {
- while(p != NULL)
- {
- Push(S, p);
- p = p->lchild;
- }
- if(!IsStackEmpty(S))
- {
- p = Pop(S); //为什么这个调用可以!
- if(!Visit(p->data))
- return ERROR;
- p = p->rchild;
- }
- }
- DestroyStack(S);
- return OK;
- }
实际上,我们把问题丢给了编译器,因为函数的返回值是在系统Stack中分配的。