码迷,mamicode.com
首页 > 其他好文 > 详细

数据结构(三):非线性逻辑结构-二叉树

时间:2015-08-02 23:29:41      阅读:205      评论:0      收藏:0      [点我收藏+]

标签:二叉树   数据结构   遍历算法   递归实现   

接着上一次对非线性逻辑数据结构树的内容,开启对二叉树的深入复习和总结。首先还是先回顾一下几个重要的概念:

一、回顾

1. 满二叉树与完全二叉树

满二叉树指的是除了叶子节点外所有的节点都有两个子节点。这样可以很容易的计算出满二叉树的深度,要掌握满二叉树的一些性质。

完全二叉树则是从满二叉树继承而来,指的所有的节点按照从上到下,从左到右的层次顺序依次排列所构成的二叉树称之为完全二叉树。所以可以想象,对于深度为h的完全二叉树,前h-1层可以构成深度为h-1的满二叉树,而对于第h层则是从左到右连续排列的。后面讲到的二叉树的存储结构当采用顺序结构时,为了能够记录二叉树节点有一个前驱和两个后继节点,以及方便插入等采用了完全二叉树的结构,将没有的节点当作虚节点,构建满二叉树,然后从上到下,从左到右依次存入即可。所以,了解满二叉树的性能是非常重要的。当然对于二叉树的存储,采用链式结构会更加的合适,后面会单独介绍。

有了这个性质,对于节点数为553的完全二叉树,可以很容易的计算出它的深度与每一层的节点数:

553 = (512-1)+ 42,所以它的深度为10,而第10层的节点数为42个,而前9层的二叉树结构构成深度为9的满二叉树。

2. 二叉树的存储

前面已经讲过了二叉树的顺序存储结构,是按照完全二叉树的结构进行的编排。这里讲解链式存储结构。

用链式存储结构存储二叉树是很自然的,节点除了数据域之外,设置了left和right两个指针域,分别存放该节点的左右孩子的地址。二叉树的链式存储结构成为二叉链表。其中root是指向根的指针,空树时root为NULL。

二叉树的二叉链表存储结构:

typedef struct tnode

{

DataType data;

struct tnode * left, * right;

}TNode;

当左孩子结点没有时,left = NULL,同理,当右孩子结点没有时,right = NULL。所以从插入、删除以及查找等各种角度上来讲,使用链式存储结构都比使用顺序存储结构要好很多。所以,以后对于二叉树的顺序存储结构只具有对比意义,在实际中都是采用链式存储结构。


二、二叉树的遍历

顺着某一条搜索路径巡访二叉树中的结点,使得每个结点均被访问一次,而且仅被访问一次,得到树中所有结点的一个线性排列,将树的结点线性化(定义一个次序)。“访问”的含义可以很广,如:输出结点的信息等。遍历是算法的基础,在线性结构中,遍历的顺序与逻辑顺序一样,所以遍历没有单独研究,而非线性遍历则复杂的多,方法也不止一种,而且算法直接与某种遍历相关联,因此把算法设计与遍历联系在一起研究。

层次遍历:从上到下、从左到右访问各结点;其示意图如下:

技术分享

先序(前序)遍历:先访问根结点,然后分别先序遍历左子树、右子树;
中序遍历:先中序遍历左子树,然后访问根结点,最后中序遍历右子树;
后序遍历:先后序遍历左、右子树,然后访问根结点。

对于下图所示的简单的二叉树,采用不同的遍历方法,得到线性排列分别为:

技术分享

层次遍历序列:A  B  C  D

先序遍历序列:A  B  D  C
中序遍历序列:B  D  A  C
后序遍历序列:D  B  C  A

三、二叉树的遍历算法

1. 递归算法

那么对于先序遍历的递归算法如下:

技术分享

<span style="font-family:SimSun;font-size:18px;"><span style="font-size:18px;">void PreOrder(CNode *pNode)
{
	if(pNode!=NULL)
		{
			printf("%lf\t",pNode->data);
			PreOrder(p->lchild);
			PreOrder(p->rchild);
		}
}</span></span>
那么对于中序遍历的递归算法如下:

<span style="font-family:SimSun;font-size:18px;"><span style="font-size:18px;">void PreOrder(CNode *pNode)
{
	if(pNode!=NULL)
		{
			PreOrder(p->lchild);
                        printf("%lf\t",pNode->data);
			PreOrder(p->rchild);
		}
}</span></span>
类似的,对于后续遍历的递归算法如下:

<span style="font-family:SimSun;font-size:18px;"><span style="font-size:18px;">void PreOrder(CNode *pNode)
{
	if(pNode!=NULL)
		{
		        PreOrder(p->lchild);
			PreOrder(p->rchild);
                        printf("%lf\t",pNode->data);
		}
}</span></span>

2. 递归算法的评估

递归函数结构清晰,代码简洁,隐蔽了复杂的细节,而且许多算法由于本身就是递归定义的,所以用递归实现更加直接更方便,但是递归函数有以下不足:

(1) 执行效率低;

(2) 一次递归调用可能产生一层接一层的递归,连续地压栈也许会超出可用堆栈空间的范围;

(3) 递归函数的参数表特殊,通常要比非递归函数所需的参数要多;

(4) 递归设计不是面向对象的方法;

(5) 有些语言不允许使用递归调用。

当强调算法设计,运行时对时间和空间的要求合理,就可以使用递归。

对于使用递归和迭代这两种实现循环的方式,可以参考我的另一篇博文《迭代是人,递归是神(迭代与递归的总结:比较)》,里面有对迭代和递归详细的分析与对比。

***********************************************************************************************************************************************************************

2015-8-2

版权声明:本文为博主原创文章,未经博主允许不得转载。

数据结构(三):非线性逻辑结构-二叉树

标签:二叉树   数据结构   遍历算法   递归实现   

原文地址:http://blog.csdn.net/lg1259156776/article/details/47210699

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!