一、结合栈的方式实现,先让右孩子入栈,再让左孩子入栈。栈为空后,结束遍历。
头文件.根据具体的函数名自己创建,另外需要使用栈,引用栈的头文件
stack.h
# pragma oncee
# include<stdio.h>
# include<stdlib.h>
# include<string.h>
# include<assert.h>
#define MAX_SIZE 10
extern struct BinTreeBTNode;//这个文件是在其他文件内部定义的,当前文件中没有,
//要到其他文件中去找
typedef struct BinTreeBTNode* PBTNode;
//指针的大小是确定的,要在当前文件中定义这种变量,必须知道当前类型的存在
//但是此类型定义于其他文件中,那么若是要定义出这种类型,编译器并不知道
//应该给多少个字节,所以此时需要使用指针(指针的大小是确定的)
typedef PBTNode DataTYpe;
typedef struct Stack
{
DataTYpe _array[MAX_SIZE];//有效元素的个数
int _top; //栈顶元素的位置
}Stack;
//初始化
void StackInit(Stack *s);
//入栈
void StackPush(Stack *s, DataTYpe data);
//出栈
void StackPop(Stack *s);
//取栈顶元素
DataTYpe StackTop(Stack *s);
//有效元素的个数
int StackSize(Stack *s);
//检测栈是否为空
int Stackempty(Stack *s);
1、前序遍历:
法1:
void PreOrderNor(PBTNode pRoot)
{
Stack s;
if (NULL == pRoot)
return;
StackInit(&s);
StackPush(&s, pRoot);//把当前根节点入栈
while (!Stackempty(&s))
{
//遍历:取栈顶元素
PBTNode pCur = StackTop(&s);//取到栈顶元素,但是元素还在栈里
printf("%c ", pCur->_data);
//移除栈顶元素:后进先出
StackPop(&s);
if (pCur->_pRight)//优先让右孩子入栈
StackPush(&s, pCur->_pRight);
if (pCur->_pLeft)
StackPush(&s, pCur->_pLeft);
}
}
法2:
void PreOrderNor(PBTNode pRoot)
{
Stack s;
if (NULL == pRoot)
return;
StackInit(&s);
StackPush(&s, pRoot);//把当前根节点入栈
while (!Stackempty(&s)){
PBTNode pCur = StackTop(&s);//取栈顶元素
StackPop(&s);//把栈顶元素移除
while (pCur){
printf("%c ", pCur->_data);
if (pCur->_pRight)
StackPush(&s,pCur->_pRight);
pCur = pCur->_pLeft;
}
}
}
2、中序遍历:找当前树最左边的节点,并保存所经路径中的所有节点
void InOrder(PBTNode pRoot)
{
PBTNode pCur = pRoot;
Stack s;
if (NULL == pRoot)
return;
StackInit(&s);
//找以pCur为根树的最左边的节点,并且保存所经路径中的所有节点
while (pCur||!Stackempty(&s))
{
while (pCur){
StackPush(&s, pCur);
pCur = pCur->_pLeft;
}
pCur = StackTop(&s);
printf("%c ", pCur->_data);
pCur = pCur->_pRight;
}
}
3、后序遍历:
void PostOrderNor(PBTNode pRoot)
{
PBTNode pCur = pRoot, pTop;
PBTNode pPrev = NULL;//标记最近访问过的节点
Stack s;
if (NULL == pRoot){
return;
}
StackInit(&s);
while (pCur||!Stackempty(&s))
{
//找最左边的节点并保存所经路径中的所有节点
while (pCur){
StackPush(&s, pCur);
pCur = pCur->_pLeft;
}
pTop = StackTop(&s);
if (NULL == pTop->_pRight||pTop->_pRight==pPrev){
printf("%c ", pTop->_data);
pPrev = pTop;
StackPop(&s);
}
else
{
pCur = pTop->_pRight;
}
}
}
根据前序、中序;后序、中序可以还原二叉树;但是通过前序、后序不能还原二叉树。
通过前序和中序还原二叉树,程序实现:
void ReBuildBinTree(pre, PreSize, in, left,right)
{
int index = 0;
//pre:前序遍历的结果,in:中序遍历的结果;Size:遍历的元素个数
if (PreSize != InSize)
return;//前序后中序遍历的元素个数不相等则返回
pre[index];//确认根节点
int i = left;
while (in[i] != pre[index])
i++;//没有找到根
//确认左右子树,在中序遍历的结果中查找
//重建根
pRoot = BuyBinTreeBTNode(pre[index]);
//重建根的左子树
++index;
ReBuildBinTree(pre, PreSize, left,i);
//重建根的右子树
++index;
ReBuildBinTree(pre, PreSize, i+1,right);
}
a、前序、后序、中序--->递归和非递归速度的比较:循环没有空间开辟、给定是静态栈时,效率比较高,涉及扩容,则效率比较低。
我是借助stack实现的非递归,但是stack可分为静态栈和动态栈,动态栈需要扩容时需要(开辟新空间(新空间需要在系统中查找哪一块空间合适,找到了则返回,查找的过程也浪费时间)、搬移元素、释放旧空间);而递归中开辟新空间只需要执行几个指令,很快。
b、递归和循环遍历的时间复杂度和空间复杂度:
时间复杂度:递归的时间时间复杂度:递归的总次数(2n)*每次递归的次数=O(n)
时间复杂度:用了一个栈,时间复杂度变高O(n)
递归容易在成栈溢出
那么可以不使用栈,用循环的方式遍历二叉树么?线索化二叉树
二、二叉树的线索化(二叉树中总共右n个节点,那么有几个空的指针域:n+1;总共有2*n个指针域,有n-1个指针域已经用过了。【通过指针域的指向,使用循环的方式实现遍历,而不是使用递归的方式】
//对节点进行改造
typedef enum {
LINK,//指向孩子
THREAD,//指向后继
}PointerFlag;
//节点的类型
typedef struct BinTreeNode
{
struct BinTreeNode* _pLeft;
struct BinTreeNode* _pRight;
DataTYpe _data;//值域
//线索
PointerFlag _leftThread;
PointerFlag _rightThread;
}BinTreeNode;
//前序线索化
void PreOrderThd(BinTreeNode* pRoot, BinTreeNode* pPrev)
{
//pPrev标记刚刚线索化过的节点
if (pRoot)
{
//线索化当前节点的左指针域
if (NULL == pRoot->_pLeft)
{
pRoot->_pLeft= pPrev;
pRoot->_leftThread = THREAD;//左孩子指向前驱节点
}
//线索化当前节点的右指针域
if (pPre&&NULL == pPrev->_pRight){
pPrev->_pRight = pRoot;
pPrev->_rightThread = THREAD;
}
pPrev = pRoot;
//判断左孩子是否存在
if (pRoot->_leftThread==LINK)//存在左孩子
//线索化当前节点的左子树
PreOrderThd(pRoot->_pLeft, pPrev);
if (pRoot->_rightThread==LINK)//存在右孩子
//线索化当前节点的右子树
PreOrderThd(pRoot->_pRight, pPrev);
}
}