看了一下午的英语和磁共振,写个博客轻松一下,
你们是想看MYSQL还是单片机还是数据结构还是安卓app?
配着和平精英车载音乐priceless,开始创作
就聊聊树和二叉树吧
树的逻辑表示方法有四种:树形表示法,文氏图表示法,凹入表示法,括号表示法。最后一种括号表示法我们写程序用的到。
树的高度了,结点个数了,存储结构了,这些知识自己看书,不再赘述,因为这是基本理论,基本算法也自己看,我们要讲就讲进阶算法。
前方高能!!!
先序,中序和后序遍历递归算法
基本内容
先序遍历访问节点的顺序是根节点-左儿子-右儿子
1 void PreOrderTraversal(BinTree BT)
2 {
3 if( BT )
4 {
5 printf(“%d\n”, BT->Data); //对节点做些访问比如打印
6 PreOrderTraversal(BT->Left); //访问左儿子
7 PreOrderTraversal(BT->Right); //访问右儿子
8 }
9 }
由递归代码可以看出,该递归为尾递归(尾递归即递归形式在函数末尾或者说在函数即将返回前)。尾递归的递归调用需要用栈存储调用的信息。
中序遍历的遍历路径与先序遍历完全一样。其实现的思路也与先序遍历非常相似。其主要的不同点是访问节点顺序不同:
中序遍历是访问完所有左儿子后再访问根节点,最后访问右儿子,即为左儿子-根节点-右儿子。
递归实现的代码如下:
void InOrderTraversal(BinTree BT)
{
if(BT)
{
InOrderTraversal(BT->Left);
printf("%d\n", BT->Data);
InOrderTraversal(BT->Right);
}
}
后序遍历与中序遍历,先序遍历的路径也完全一样。主要的不同点是后序遍历访问节点的顺序是先访问左儿子和右儿子,
最后访问节点,即左儿子-右儿子-根节点。
递归实现思路与中序遍历和先序遍历相似,代码如下:
void PostOrderTraversal(BinTree BT)
{
if (BT)
{
PostOrderTraversal(BT->Left);
PostOrderTraversal(BT->Right);
printf("%d\n", BT->Data);
}
}
基础知识结束
现在问题来了,为什么有递归的也有非递归的,他们有什么区别?
如果我使用递归算法,毫无疑问,简洁,简单,易懂。但是缺点就是是函数调用自身,而函数调用是有时间和空间的消耗的:每一次函数调用,都需要在内存栈中分配空间以保存参数、返回地址以及临时变量,而往栈中压入数据和弹出数据都需要时间。调用栈可能会溢出,其实每一次函数调用会在内存栈中分配空间,而每个进程的栈的容量是有限的,当调用的层次太多时,就会超出栈的容量,从而导致栈溢出。那到时候你怎么办?对吧。
要不要分析一下非递归算法?我分析一个,然后其他的自己看书。
非递归先序遍历算法基本思路:使用堆栈
void PreOrderTraversal(BinTree BT)
{
BinTree T = BT;
Stack S = CreatStack(MAX_SIZE); //创建并初始化堆栈S
while(T || !IsEmpty(S))
{
while(T) //一直向左并将沿途节点访问(打印)后压入堆栈
{
printf("%d\n", T->Data);
Push(S, T);
T = T->Left;
}
if (!IsEmpty(S))
{
T = Pop(S); //节点弹出堆栈
T = T->Right; //转向右子树
}
}
}
a. 遇到一个节点,访问它,然后把它压栈,并去遍历它的左子树;
b. 当左子树遍历结束后,从栈顶弹出该节点并将其指向右儿子,继续a步骤;
c. 当所有节点访问完即最后访问的树节点为空且栈空时,停止。
简单来说,
看这个图,访问顺序是ABDGCEF。根节点是A,毋庸置疑,进栈再访问,A进去又出来了。接着到左右孩子B,C,C先进去,因为从先序遍历次序根左右来说,栈是先进后出,左孩子先访问,所以左孩子后进去,好了,然后B出去了,D进栈,再出栈。G进栈,再出栈。好了,这个时候C终于可以出栈了,接着F,E进栈,E,F出栈。完美结束。
中序非递归的话,教你一个判断自己错没错的小技巧,那个根节点最后一定是在最中间,如果你不是,那恭喜你,一定错了。
后序非递归的话,简单粗暴的说,根节点和他的左孩子的左孩子们进栈,但是先别访问,让左孩子们都先出栈,右孩子们进栈,右孩子们再出栈,最后根节点出栈。
我感觉都是一类,一样的,中序和后序甚至比先序还好理解。加油吧!