生活的天平本不平衡,只有通过努力改变其偏向。

继续二叉树的三种遍历之非递归法

2008-11-02

前两天写了一个二叉树的递归遍历法,虽然递归的算法让人容易理解,代码简短。但有一个致命缺陷,那就是遍历的二叉树数据不能太大,否则会造成栈溢出,使程序崩溃。如果这种情况出现,问题比较难跟踪。致于遍归层数太多为什么会造成栈崩溃的原因很好理解,由于函数据的调用是靠栈进行传递参数的,栈的分配内存地址是逐渐减小的,终会有分配完的一天。所以采用递归进行遍历会有一定的隐患。

今天讲的非递归法,就是根据不同的遍历法则,将数据“压栈”,什么还是栈来传递?和递归有何区别?其实看下面的代码就可以知道,我这个栈,其实是在堆上维护的一个链表,堆的空间取决于物理内存和虚拟内存的大小。

二叉树我还是用的是前两天二叉树的递归遍历法中生成的那棵。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
//功能:分别采用三种方法非递遍历二叉树,前序,中序,后序三种方法遍历。
//作者:lonkil
//网站: Http://Www.VcFans.Com
//日期: 2008-11-2
//===============================================================
#include "stdafx.h"
#include <malloc.h>
 
//定义一个结构保证数据二叉数的一个节点。
typedef struct  _Bin_Tree
{
	_Bin_Tree *pLeft;//左子树
	_Bin_Tree *pRight;//右子树
	int nData;//节点数据
}Bin_Tree;
 
typedef struct _STACK
{
	Bin_Tree *pData;
	_STACK *pNext;
} STACK;
 
//将一个节点置0
void ZeroNode(Bin_Tree *pNode)
{
	pNode->nData = 0;
	pNode->pLeft = 0;
	pNode->pRight = 0;
}
 
//生成一个二叉树
void CreateATree(Bin_Tree *pNode,int n)
{
	Bin_Tree *pLeft=0,*pRight=0;
	if ( pNode == 0 ) return;
 
	pLeft = (_Bin_Tree *)malloc( sizeof(Bin_Tree) );
	ZeroNode( pLeft );
	pLeft->nData = n;
	pRight = (_Bin_Tree *)malloc( sizeof(Bin_Tree) );
	ZeroNode( pRight );
	pRight->nData = n+1;
 
	pNode->pLeft = pLeft;
	pNode->pRight = pRight;	
}
 
//释放一个二叉树所占的内存
void ReleaseTree(Bin_Tree **pNode)
{
	if ( (*pNode) == 0 ) return;
	ReleaseTree( &((*pNode)->pLeft) );
	ReleaseTree( &((*pNode)->pRight) );
	free( (*pNode) );
	(*pNode) = 0;
}
 
//压栈
void push( STACK * pS,STACK **pTop )
{
	if( (*pTop) == 0 )
	{
		(*pTop) = pS;
	}
	else
	{
		pS->pNext = *pTop;
		(*pTop) = pS;//将栈指针上移
	}	
}
 
//出栈
STACK * pop( STACK **pTop )
{
	if ( (*pTop) == 0 ) return 0;
	(*pTop) = (*pTop)->pNext;//栈顶指针下移
	return (*pTop);
}
 
//栈遍历前序
//最要思想:
//第一步将树的左分支的左节点压入栈,最后栈顶将是树最左边节点
//然后从底部将栈中的每个节点放弹出,遍历其右节点,如果右节点还有分支,重复第一步.
void PreOrder_stack( Bin_Tree *pRoot )
{
	STACK *pSTop=0,*pSTemp=0;
	Bin_Tree *pTemp = pRoot;
	if ( pRoot == 0) return;
 
	while ( pTemp || pSTop != 0  )
	{
		if( pTemp )
		{
			STACK *pStack = (STACK *)malloc( sizeof( STACK ) );
			pStack->pData = pTemp;
			pStack->pNext = 0;
			printf("%d,",pTemp->nData);
			push( pStack, &pSTop );
			pTemp = pTemp->pLeft;
		}
		else
		{
			Bin_Tree *pSubRight = pSTop->pData->pRight;
			pSTemp = pSTop;
			pSTop = pop( &pSTop );
			pTemp = pSubRight;
			free(pSTemp);
			pSTemp = 0;
		}
	}
}
 
//栈遍历中序
//主要思想是将按左边寻址的方式,进行寻址,将每个节点压入栈。
//当左子到头后,再出弹栈,这样必然是先左、后中,再右。
void InOrder_stack( Bin_Tree *pRoot )
{
	STACK *pSTop=0,*pSTemp=0;
	Bin_Tree *pTemp = pRoot;
	if ( pRoot == 0) return;
 
	while ( pTemp || pSTop != 0  )
	{
		if( pTemp )
		{
			STACK *pStack = (STACK *)malloc( sizeof( STACK ) );
			pStack->pData = pTemp;
			pStack->pNext = 0;
			push( pStack, &pSTop );
			pTemp = pTemp->pLeft;
		}
		else
		{
			pSTemp = pSTop;
			pSTop = pop( &pSTop );
			printf("%d,",pSTemp->pData->nData);
			pTemp = pSTemp->pData->pRight;
			free(pSTemp);
			pSTemp = 0;
		}
	}
}
 
//栈遍历后序
//主要思想是先遍历左右两个子树,再遍历根节点。
//后序遍历有一个需要主意的地方,就是从右树遍历完后,
//开始弹栈的时候,需要加一个pPre标记,该无数已经访问过,否则无法遍历完成。
void PostOrder_stack( Bin_Tree *pRoot )
{
	STACK *pSTop=0,*pSTemp=0;
	Bin_Tree *pTemp = pRoot, *pPre=0;
	if ( pRoot == 0) return;
 
	while ( pTemp || pSTop != 0  )
	{
		while( pTemp )
		{//遍历左子树
			STACK *pStack = (STACK *)malloc( sizeof( STACK ) );
			pStack->pData = pTemp;
			pStack->pNext = 0;
			push( pStack, &pSTop );
			pTemp = pTemp->pLeft;
		}
		if ( pSTop->pData->pRight == 0 || pPre == pSTop->pData->pRight )
		{//没有右子树或该节点刚被访问过
			printf("%d,", pSTop->pData->nData);
			pSTemp = pSTop;
			pPre = pSTop->pData;
			pSTop = pop( &pSTop );
			pSTemp = 0;	
		}
		else
		{
			pTemp = pSTop->pData->pRight;
		}
	}
}
 
int main(int argc, char* argv[])
{
	Bin_Tree *pRoot=(Bin_Tree *) malloc( sizeof(Bin_Tree) );
	ZeroNode(pRoot);
	pRoot->nData = 0;
	//手工建立深度为3的完全二叉树
	CreateATree( pRoot, 1 );
	CreateATree( pRoot->pLeft,3);
	CreateATree( pRoot->pRight,5);
 
	printf("非递归前序遍历: ");
	PreOrder_stack( pRoot );
	printf("\n");
 
	printf("非递归中序遍历: ");
	InOrder_stack( pRoot );
	printf("\n");
 
	printf("非递归后序遍历: ");
	PostOrder_stack( pRoot );
	printf("\n");
 
	ReleaseTree( &pRoot );
	return 0;
}
作者:lonkil | 分类目录:本站原创 | 标签:

12 条评论

  1. lonkil 说道:

    TO:C瓜哥

    最近懒了。

  2. C瓜哥 说道:

    不好意思啊,点了一个链接链接到你如此陈旧的一篇博文上面来了。我还以为你更新了……

  3. C瓜哥 说道:

    我现在只记得住递归遍历的方法了,虽然一学期前才上完《数据结构》的课程

  4. Tr0j4n 说道:

    大哥这个博客有bug,我想把自己写的Stack代码贴出来,不使用双重指针的,可以贴了很久都发表不出来!!
    郁闷!

    • lonkil 说道:

      不好意思,是我的评论插件的问题将你的评论转成了待审核状态了,我看在后台能否修改。

  5. Tr0j4n 说道:

    向大哥学习了,不过我用栈就没有用过双重指针的
    struct Stack {
    ElemType *stack ; // 存栈元素
    int top; // 栈顶指示器
    int MaxSize; // 栈的最大长度
    };

    void Push(Stack &S, ElemType item) //元素 item进栈
    {
    if(S.top==S.MaxSize-1){
    int s=sizeof(ElemType);
    S.stack=(ElemType *)realloc(S.stack,2*S.MaxSize*s);
    }
    S.top++;
    *(S.stack+S.top)=item;
    }
    ElemType Pop(Stack &S) //栈S的栈顶元素出栈并返回
    {
    S.top–;
    return S.stack[S.top+1];
    }
    ElemType Peek(Stack S) //取栈S的当前栈顶元素并返回
    {
    return S.stack[S.top];
    }
    void ClearStack (Stack &S) //清除栈s,使成为空栈
    {
    for(int i=0;i<S.MaxSize;i++)
    free(S.stack+i);
    }

    • lonkil 说道:

      哈哈,看到了兄弟的解决方案。你是用了C++的引用。

      嗯,你将栈的信息保存在一个Struct里面,这也是一个实现一个思路,不错。
      ElemType Pop(Stack &S) //栈S的栈顶元素出栈并返回
      {
      S.top–;
      return S.stack[S.top+1];
      }
      在弹栈时是不是应该加一个数量检测,不然后程序会弹飞掉的。
      你采用预分配内存机制,避免重复分配内存,这种方式不错,有点内存池的味道,学习了。

  6. Tr0j4n 说道:

    发表一点陋见,大哥写程序的风格有点…..太混杂了,(或者冒昧地说有点不符合规范?)举个例子,申请你用malloc,那么规范的C就是delete[]。但是你用free,开始申请一般是用new的。本菜鸟也不是很习惯看**这样的东东,呵呵。
    PS:出栈函数STACK * pop( STACK **pTop )中的第一句return 0是否妥当?

    • lonkil 说道:

      兄弟,过谦了。什么陋见?以兄弟的水平是指导才对。
      我把我的想法发表出来,不一定就是对的,仅仅是我自己认为是对的,所以大家可以发表自己的看法。这样才能有进步,才有提高嘛。

      关于兄弟这个回复:
      我看兄弟是不是将C和C++搞混了?new与delete配对,malloc与free配对。
      在C++中可以采用malloc/free和new/delete两种内存分配形式,而C中只能使用malloc这种内存分配形式。
      由于new/delete在释放空间之前,会先调用对象的构造或析构函数,再释放内存空间。所以在内存申请或释放时比较常见。
      但不可将malloc和delete ,new和free这种进行混合搭配使用。

      C++的delete [] pXX,这种情况是用来释放 char *pXX = new char [100],这种形式的连续申请的空间。

      兄弟你以为我想用双重指针呀?这是没有办法的,正如上篇中BaihowFF在评论中所说,以函数参数形式传入的指针只副本,想修改其指向必需要指针的指针。所以就有了**之类的东西。哈哈

      出栈函数STACK * pop( STACK **pTop )中的第一句return 0是否妥当?
      引起这一点的误会,是由于我的原因,上次回复BaihowFF中已经说到了,我太懒的原因,哈哈。

      如果这样写兄弟可能就不会这么认为了:
      #define NULL 0
      //出栈
      STACK * pop( STACK **pTop )
      {
      if ( (*pTop) == NULL ) return NULL ;
      (*pTop) = (*pTop)->pNext;//栈顶指针下移
      return (*pTop);
      }

    • waterathena 说道:

      你搞错了吧。malloc 和 free是一对,NEW 和Delete才是一对。觉得你应该去看下书。