数据结构之二叉树——概念原理及代码实现

树的简介

之前已经写过几篇关于数据结构的文章,分别是链表、栈和队列。其中链表采用链式存储结构,栈和队列既可以使用链式存储也可以使用顺序存储(数组),而这些结构有一个共同的特性,看下图

在这里插入图片描述
从逻辑上讲,无论是链式存储还是顺序存储,这些结构在非空得情况下,每个节点最多只有一个前驱和一个后继,也就是说它们一对一的。而今天所讲的树属于非线性结构,树在非空的情况下,除了根节点(相当于线性表中的头结点)之外,每个节点都有一个前驱零个或多个后继,它们是一对多的。见下图
在这里插入图片描述
上图的A代表的就是树的根节点,每棵树只有一个根节点,而且当树不只有根节点时,其余结点可分为m(m>0)个互不相交的有限集T1、T2、…、Tm,其中每一个集合本身又是一棵树,并且称为根的子树。下图就是上图根节点的两个子树。
在这里插入图片描述
同理每棵子树也可以成立为一颗独立的树,并且也可以拥有自己的子树。在树中,结点的子树的根称为该结点的孩子(child),相应的,该结点称为孩子的双亲(parent)同一双亲的孩子互称兄弟(sibling),结点的祖先是从根到该结点所经分支上的所有结点,反之以某结点为根的子树上的任一结点都称为结点的子孙,而没有任何孩子的结点我们称为叶子结点或简称叶子。
在这里插入图片描述
如图,各结点拥有的子树数称为结点的(Degree)。树的度是树内各结点度的最大值。该树的度为3。我们还可以将树分层,根结点为第一层,根的孩子在第二层,类推,若某结点在第l层,则该结点的孩子在l+1层,其次,双亲不同但双亲在同一层的结点互称堂兄弟。见下图,该树为4层。
在这里插入图片描述
由树的定义,树中结点的孩子可以为多个,所以树的结构繁多,二叉树、线索二叉树,二叉树的特殊形式平衡二叉树和红黑树、 B树和B+树等。今天我们主要介绍普通的二叉树,它也是应用最广泛的树,其他树我们在后续的文章中再给大家介绍。

二叉树

二叉树是最简单的树,也是使用最广泛的一种树,二叉树是指每个结点最多有两个孩子,所以二叉树得度不会大于2。下图是一个二叉树
在这里插入图片描述
二叉树的左子树和右子树是有顺序的,二者不能颠倒。

满二叉树和完全二叉树

二叉树中有两个特殊的结构——满二叉树完全二叉树
满二叉树是指除叶子外所有分支节点都有左子树和右子树,并且所有叶子都在同一层。上图就是一个满二叉树。完全二叉树则是相对于满二叉树而言的。我们先将满二叉树中的各结点从左至右并从上至下编号,见下图
在这里插入图片描述
现在我们按序号从大到小的顺序依次删除若干结点,例如我们删除15、14、13、12、11号结点,剩下的就是一个完全二叉树,见下图
在这里插入图片描述
同样我们删除一个或两个剩下的也是完全二叉树,但如果我们不按照序号从大到小的顺序删除,那剩下的就不是完全二叉树,见下图
在这里插入图片描述

如图我们删除15,14,13,11号结点后,原来的12号结点K变成了11号结点,这和K在满二叉树中的编号不同,这种二叉树不是完全二叉树。现在我们总结一下完全二叉树的概念:对一棵具有n个结点的二叉树按层序编号,如果编号为i(1<=i<=n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置一样,则这棵二叉树就是完全二叉树

二叉树主要性质

有几个二叉树非常重要的性质大家一定要记住,至于原理其实很简单,大家可以试着去推到一下,或阅读相关资料

  • 性质1:二叉树第i层上的结点数目最多为 2^{i-1} (i≥1)
  • 性质2:深度为k的二叉树至多有2^k-1个结点(k≥1)
  • 性质3:包含n个结点的完全二叉树的高度为[log2 n]+1
  • 性质4:在任意一棵二叉树中,若终端结点的个数为n0,度为2的结点数为n2,则n0=n2+1
  • 性质5:如果对一棵有n个结点的二叉树(其深度为[log2n]+1的结点按层序从左至右编号,对任一结点有:
    1.如果i=1,则结点时二叉树的根,无双亲;如果i>1,则其双亲是结点[i/2];
    2.如果2i>n,则结点i无左孩子(结点i为叶子结点),则其双亲是结点2i;
    3.如果2i>n,则结点i无右孩子;否则其右孩子是结点2i。

    注:[x]为不大于x的最大整数。

二叉树存储结构

二叉树存储也分链式存储和顺序存储(数组)。但二叉树的顺序存储的适应性不强,我们主要使用链式存储。我们将二叉树结点分为一个数据域和两个指针域,数据域存储该结点的数据,左指针存储指向左孩子的指针,右指针存储指向右孩子的指针。结构如图
在这里插入图片描述
结构定义代码为:


typedef struct BinaryTreeNode{
	int                   data;
	BinaryTreeNode*       lchild;
	BinaryTreeNode*       rchild;
}*BiTree;

二叉树遍历

因为二叉树是链式存储,所以如果我们要访问某个结点也要通过遍历的方式,即从根节点开始,依照某种次序依次访问。而二叉树经常使用的访问次序有前序、中序和后序。下面依次介绍。

前序遍历

如图,从根节点开始访问,先遍历左子树再遍历右子树,若空则返回。遍历顺序为:ABDGHCEIF。
在这里插入图片描述

代码实现

可以看到遍历的方式也体现了递归的思想,我们把一棵大的二叉树分为若干层互相统属小的子树,对于每一个子树我们想访问根节点再访问左子树最后访问右子树。因此我们使用递归可以很简介的写出代码:

void PreOrderTraverse(const BiTree T)
{
	if (T == NULL)
		return;
	cout << T->data;
	PreOrderTraverse(T->lchild);
	PreOrderTraverse(T->rchild);
}

大家如果对递归的调用不是很理解得话,可以尝试逐步调试一下。

中序遍历

如图,从根节点开始(注意从根节点开始但不访问,对二叉树的所有操作都要从根节点开始),先遍历左子树再访问根节点最后遍历右子树,若空则返回。遍历顺序为:GDHBAEICF
在这里插入图片描述

代码实现

仍然使用递归

void InOrderTraverse(const BiTree T)
{
	if (T == NULL)
		return;
	InOrderTraverse(T->lchild);
	cout << T->data;
	InOrderTraverse(T->rchild);
}

后序遍历

如图,从根节点开始,先遍历左子树再遍历右子树最后访问根节点,若空则返回。遍历顺序为:GHDBIEFCA。
在这里插入图片描述

代码实现

仍然采用递归

void PostOrderTraverse(const BiTree T)
{
	if (T == NULL)
		return;
	PostOrderTraverse(T->lchild);
	PostOrderTraverse(T->rchild);
	cout << T->data;
}

二叉树的建立

建立二叉树有很多种方式,因为遍历使用了递归,所以我们仍使用递归的方式建立。它的思想和遍历一样,实现代码如下:

void CreateBiTree(BiTree &T)
{
	int ch;
	cin >> ch;
	if (ch == '#')
		T = NULL;
	else
	{
		T = (BiTree)new BiTNode;
		if (!T)
			exit(OVERFLOW);
		(T)->data = ch;
		CreateBiTree(T->lchild);
		CreateBiTree(T->rchild);
	}
}

(本文中使用所有图片均来自网络)

总结

二叉树的概念较为繁多和复杂,但鉴于二叉树的使用范围很广,建议大家多多练习,可以自己封装一个二叉树,去实现它的各项操作,会对二叉树有更深的了解。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值