二叉树的基本操作的实现

树的定义

(Tree)是n(n>=0)个结点的有限集。当有限集T为空时称为空树
当T不为空时,它满足两个条件:

  1. 有且仅有一个特定的称为根(root)的结点;
  2. 其余的结点可分为m(m>=0)个互不相交的子集T1,T2,T3…Tm,其中每个子集又是一棵树,并称其为子树(Subtree)。

基本术语

  1. 树的结点包含一个数据元素及若干指向其子树的分支
  2. 度为0的结点称为叶子结点(leaf)或终端结点
  3. 结点所拥有的子树的个数称为该结点的度。结点度的最大值为树的度
  4. 结点的层次:根为第一层,根的孩子为第二层。树中结点的最大层次称为树的深度
  5. 树中一个结点的子树的根结点称为这个结点的孩子(child)。这个结点称为它孩子结点的双亲(Parent)。具有同一个双亲的孩子结点互称为兄弟(sibling)。
  6. 如果将树中结点的各子树看成从左至右是有次序的,则称该树为有序树,否则称为无序树。

二叉树

二叉树的定义

二叉树(Binary Tree)是一种特殊的树型结构,它的特点是每个结点至多只有两棵子树(即二叉树中不存在度数大于2的结点),并且,二叉树的子树有左右之分

二叉树就是度为2的树吗?

不是
一棵度为二的有序树与一棵二叉树的区别在于:有序树的结点次序是相对于另一结点而言的,如果有序树中的子树只有一个孩子时,这个孩子结点就无须区分其左右次序,而二叉树无论其孩子数是否为2,均需确定其左右次序,也就是说二叉树的结点次序不是相对于另一结点而言而是确定的。

满二叉树和完全二叉树

满二叉树:深度为k,且有(2^k)-1个结点的二叉树。
满二叉树
完全二叉树:深度为k,结点数为n的二叉树,当且仅当每个结点的编号都与相同深度的满二叉树中从1到n的结点一一对应时,称为完全二叉树。
在这里插入图片描述
下面两个二叉树不是完全二叉树,因为没有与相同深度的满二叉树中的结点一 一对应
在这里插入图片描述在这里插入图片描述

二叉树的性质

  • 在二叉树的第i层上至多有 2i-1个结点。
  • 深度为k的二叉树至多有 2k-1个结点(k>1)。
  • 对任何一棵二叉树T, 如果其叶结点数为 n0, 度为2的结点数为 n2,则n0=n2+1。
  • 具有 n (n >=0) 个结点的完全二叉树的深度为|log2(n)|(取整)+1
  • 如果对一棵有n个结点的完全二叉树的结点按层次顺序编号,则对任一结点有:
    如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结点 i/2(取整)
    如果2i>n,则结点i无左孩子;否则其左孩子是结点2i(取整)
    如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1(取整)

给个图例,自行体会~
在这里插入图片描述

二叉树的存储结构

顺序存储表示

完全二叉树的结点层次编号,用一组地址连续的存储单元,依编号存放二叉树中的数据元素,结点的相对位置蕴含着结点之间的关系。

如图

它的存储序列为:
在这里插入图片描述

链式存储表示

可根据不同的结点结构设计不同形式的链式存储结构

对于下图的二叉树,有几种不同的链式存储结构
在这里插入图片描述

二叉链表

二叉链表
在这里插入图片描述
二叉树的类型描述

typedef char TElemType;
typedef struct BiNode{
	TElemType data;
	struct BiNode* lchild;          //左孩子
	struct BiNode* rchild;          //右孩子
}BiNode,*BiTree;

三叉链表

在这里插入图片描述
在这里插入图片描述
三叉链表的类型描述

typedef char TElemType;
typedef struct BiNode{
	TElemType data;
	struct BiNode* lchild;          //左孩子
	struct BiNode* rchild;          //右孩子
	struct BiNode* parent;          //父结点
}BiNode,*BiTree;

线索链表

二叉链表中的空链域存储其他有用信息

遍历二叉树

根据某种路径来巡防二叉树中的每一个结点,使得每一个结点均被访问,且只访问一次。二叉树的遍历是对二叉树进行多种操作的基础

本文采用的是二叉链表的存储结构

如下图所示,有三种遍历方式
在这里插入图片描述

  1. 先序遍历 DLR
  2. 中序遍历 LDR
  3. 后序遍历 LRD

再举一个例子
在这里插入图片描述

先序遍历

实际上是一种递归算法思想

  1. 访问根结点
  2. 先序遍历左子树
  3. 先序遍历右子树

若遍历二叉树,则需要对二叉树进行一定的操作,这里就对二叉树进行输出操作

status Print(TElemType e)
{   //输出数据值
	cout << e;
	return OK;
}

void PreOrderTraverse(BiTree T, status (*Print)(TElemType e))
{  //先序遍历算法
	if (T)
	{
		Print(T->data);
		PreOrderTraverse(T->lchild, Print);
		PreOrderTraverse(T->rchild, Print);
	}
}

中序遍历

  1. 中序遍历左子树
  2. 访问根结点
  3. 中序遍历右子树
void InOrder(BiTree T, status(*Print)(TElemType e))
{   //中序遍历算法
	if (T)
	{
		InOrder(T->lchild, Print);
		Print(T->data);
		InOrder(T->rchild, Print);
	}
}

后序遍历

  1. 后序遍历左子树
  2. 后序遍历右子树
  3. 访问根结点
void PostOrder(BiTree T, status(*Print)(TElemType e))
{    //后序遍历算法
	if (T)
	{
		PostOrder(T->lchild, Print);
		PostOrder(T->rchild, Print);
		Print(T->data);
	}
}

创建二叉树

这里默认用先序的方式创建二叉树。

思路如下:

  1. 首先判断二叉树是否为空(即输入的第一个数据a是否为’#’)。若第一个数据a为空格,则二叉树为NULL;若不为空,进行下列操作
  2. 为根建立结点,其数据域为a
  3. 先序创建左子树的二叉树链表
  4. 先序创建右子树的二叉树链表

(即在满二叉树的相应结点位置如果为空,就输入’#’)

void CreateBiTree(BiTree &T)
{   //先序创建二叉树
	TElemType a;
	cin >> a;
	if (a == '#') T = NULL;
	else {
		T = (BiNode*)malloc(sizeof(BiNode));
		if(!T) exit(0);
		T->data = a;
		CreateBiTree(T->lchild);
		CreateBiTree(T->rchild);
	}
}

假如要创建如下图的二叉树
在这里插入图片描述
在这里插入图片描述
则需要输入以下:
ABC##DE#G##F###

统计二叉树中叶子结点的个数

我们先来回顾一下叶子结点的定义:度为0的结点。
故当一个结点左孩子和右孩子都不存在时,这个结点就为叶子结点。
因为要返回叶子结点的个数,所以需在算法中增添一个“计数器”,并将算法中对结点的操作改为:若是叶子结点,则计数器加1。

注意:要为计数器专门定义一个全局变量,默认值为0,不要让它的赋值语句出现在函数体或主函数中!

int num=0;     //计数器num为全局变量
void countleaf(BiTree T, int& num)
{   //返回叶子结点的个数
	if (T)
	{
		if (!T->lchild && !T->rchild) num++;
		countleaf(T->lchild, num);
		countleaf(T->rchild, num);
	}
}

求二叉树树的深度

当二叉树为空树时,深度为0;当二叉树只有根结点时,深度为1;
其他情况时,求左子树和右子树中深度的最大值,再加1,即为树的深度

int  Depth(BiTree T)
{
	int left, right,h;
	if (!T) h = 0;
	else if (!T->lchild && !T->rchild) h = 1;
	else {
		left = Depth(T->lchild);
		right = Depth(T->rchild);
		h = 1 + (left > right ? left : right);
	}
	return h;
}

主函数

void main()
{
	BiTree T;
	int i;
	T = (BiNode*)malloc(sizeof(BiNode));
	cout << "1.创建二叉树  2.输出(先序)  3.输出(中序)  4.输出(后序) 5.返回叶子结点的个数  6.返回二叉树的深度  7.退出" << endl;
	while (1)
	{
		cout << "请输入操作:";
		cin >> i;
		switch (i)
		{
		case 1: cout << "请输入树的结点:";  CreateBiTree(T); break;
		case 2: cout << "先序遍历输出为:"; PreOrderTraverse(T, Print); cout << endl; break;
		case 3: cout << "中序遍历输出为:"; InOrder(T, Print); cout << endl;  break;
		case 4: cout << "后序遍历输出为:"; PostOrder(T, Print);  cout << endl; break;
		case 5: cout << "叶子结点的个数为:"; countleaf(T,num);  cout<<num << endl; break;
		case 6: cout << "该二叉树的深度为:" << Depth(T); cout << endl;  break;
		case 7: exit(0); break;
		default: cout << "请重新输入!" << endl; break;
		}
	}
}

运行结果如下:
在这里插入图片描述
总而言之,二叉树中,最重要的思想是递归,并且所有操作的基础就是遍历

我总是喜欢把很多东西慢慢攒起来,等到时机成熟的时候一股脑开启,就像是专门为这一刻准备了这么久一样。这很好,但是这总会给我一种幻觉,仿佛我这一段时间,这些年,甚至这一辈子,就是为此而活的。如今我才明白,这是目标,但不是终极目标,更不是那种达成目标之后如释重负到脱离苦海的感觉。你要知道,人生的终极目标是死亡啊,生活并不是达到高潮就开始享受,堕入低谷就昏天黑日。
一生冗长,不失爱与自由,向死而行的生命都在热烈地生长。

  • 9
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值