目录
一、树的定义与基本术语
为了对二叉树有更好地理解先简单介绍非线性数据结构-----树型结构。
1、树的定义
树(tree):是n (n≥0) 个结点的有限集T。
在任意一棵非空树中:
1.有且仅有一个特定的结点,称为树的根(root)
2.当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1,T2,…Tm,其中每一个集合本身又是一棵树,称为根的子树(subtree)
2、基本术语
结点(node):表示树中的元素,包括数据项及若干指向其子树的分支
孩子(child):结点子树的根
父结点(parent):结点的上层直连的结点
兄弟(sibling):同一父结点的孩子
结点的度(degree):结点拥有的子树数
叶子(leaf):度为0的结点
树的度:一棵树中最大的结点度数
结点深度/层次(level):从根结点算起,根为第一层,它的孩子为第二层……
结点高度(height):该结点作为根的子树的高度
树的高度=树的深度=最大结点层数
二、二叉树的定义
二叉树(Binary Tree)是一种特殊的树形结构,它是n个结点的有限集,它或为空树(n=0),或由一个根结点和两棵分别称为左子树和右子树的互不相交的二叉树构成。其特点是:
每个结点至多有二棵子树
二叉树的子树有左、右之分,且其次序不能任意颠倒
上一节有关树的基本术语也都适用于二叉树。
三、二叉树的存储结构
二叉树的存储结构也可采用顺序存储和链式存储两种方式,本文介绍链式存储结构——二叉链表。
由二叉树的定义得知,二叉树的结点由一个数据元素和分别指向其左、右子树的两个分支构成,则表示二叉树的二叉链表结点中包含3个域:数据域和左、右孩子指针域。见下图。
下面给出一棵二叉树的二叉链表结构图。
由上图可以观察出一个结论:在含有n个结点的二叉链表中有n+1个空链域。(利用这些空链域存储其它有用信息可得到线索二叉树,下一次我将更新此内容)
证明:含有n个结点的二叉链表中共有2n个指针域,在此链表中用了n-1个指针域将n个结点连接起来,故还剩 2n-(n-1)=n+1 个空链域。
接下来介绍的二叉树操作的算法均采用以下定义的二叉链表形式实现。
//二叉树的二叉链表结点
typedef char TElemType;
typedef struct BiTNode
{
TElemType data;//数据域
BiTNode* lchild;//左孩子
BiTNode* rchild;//右孩子
}BiTNode, * BiTree;
四、二叉树的基本操作
1、先序遍历
先访问根结点,然后分别先序遍历左子树、右子树。(从这棵树根结点开始,先访问这个根结点(对结点进行操作),再访问它的左子树,最后访问右子树。运用递归思想,上述过程中遇到的每一个结点都是某一棵子树的根结点,重复以上访问顺序便完成先序遍历)
//先序遍历
void preOrderTraverse(BiTree T, void (*visit)(TElemType))
{//参数visit是一个函数指针
if (T)//根结点若为空则不执行任何操作
{
//通过visit函数指针对根结点数据进行访问,如果对函数指针不熟可换成一条打印语句
visit(T->data);
preOrderTraverse(T->lchild, visit);//递归遍历左子树
preOrderTraverse(T->rchild, visit);//递归遍历右子树
}
}
2、中序遍历
首先中序遍历左子树,然后访问根结点,最后中序遍历右子树。(从这棵树根结点开始,先访问它的左子树,再访问这个根结点(对结点进行操作),最后访问右子树。运用递归思想,上述过程中遇到的每一个结点都是某一棵子树的根结点,重复以上访问顺序便完成先序遍历)
//中序遍历
void inOrderTraverse(BiTree T, void (*visit)(TElemType))
{
if (T)//根结点若为空则不执行任何操作
{
inOrderTraverse(T->lchild, visit);//递归遍历左子树
visit(T->data);//访问根结点
inOrderTraverse(T->rchild, visit);//递归遍历右子树
}
}
3、后序遍历
首先后序遍历左、右子树,然后访问根结点。(从这棵树根结点开始,先访问它的左子树,再访问右子树,最后访问这个根结点(对结点进行操作)。运用递归思想,上述过程中遇到的每一个结点都是某一棵子树的根结点,重复以上访问顺序便完成先序遍历)
//后序遍历
void postOrderTraverse(BiTree T, void (*visit)(TElemType))
{
if (T)//根结点若为空则不执行任何操作
{
postOrderTraverse(T->lchild, visit);//递归遍历左子树
postOrderTraverse(T->rchild, visit);//递归遍历右子树
visit(T->data);//访问根结点
}
}
4、层次遍历
从上到下、从左到右访问各结点。(层次遍历过程较为简单,运用队列便可实现,具体过程见代码注释)
//层次遍历
void levelTraverse(BiTree T, void(*visit)(TElemType) )
{
Queue<BiTNode*> Q;//定义队列Q,队列代码在我主页有
BiTNode* p = T;//初始化结点指针p指向根结点
while (p || !Q.empty())
{//p为空指针并且队列为空是退出条件,表示遍历完毕
if (p)//若结点不为空
{
visit(p->data);//访问该结点
Q.enQueue(p->lchild);//结点左孩子入队
Q.enQueue(p->rchild);//结点右孩子入队
Q.deQueue(p);//队头元素出队,用p接收
}
else//此时结点为空,但队列不为空
Q.deQueue(p);//队头元素出队,用p接收
}
}
5、按先序次序创建二叉树
//按先序次序创建二叉树,注意:'#'代表空结点
void createBiTree(BiTree& T)
{//引用方式传入树的根结点
TElemType ch;//接收输入字符
cin >> ch;
if (ch == '#')//'#'代表结点为空
T = NULL;
else
{
T = new BiTNode;//为根结点T开辟内存空间
T->data = ch;//为节点赋值
createBiTree(T->lchild);//递归创建左子树
createBiTree(T->rchild);//递归创建右子树
}
}
6、深度计算
//计算二叉树的深度,在后序遍历基础上进行
int depth(BiTree T)
{
if (T == NULL)//为空返回0,递归结束
return 0;
else
{
int m = depth(T->lchild);//递归计算左子树的深度记为m
int n = depth(T->rchild);//递归计算右子树的深度记为n
return m > n ? m + 1 : n + 1;//二叉树的深度为m与n的较大者加1
}
}
7、总的结点个数
//统计二叉树中总的结点个数
int countNode(BiTree T)
{
if (T == NULL)
return 0; //如果是空树,结点个数为0,递归结束
else//否则返回左子树的结点个数+右子树的结点个数+1,+1为加上当前根节点
return countNode(T->lchild) + countNode(T->rchild) + 1;
}
8、叶子结点个数
//统计叶子结点个数
int countNode0(BiTree T)
{
if (T == NULL)//结点为空返回0,递归结束
return 0;
else if (!T->lchild && !T->rchild)//左右子树不存在代表为叶子结点
return 1 + countNode0(T->lchild) + countNode0(T->rchild);
else//遍历左右子树
return countNode0(T->lchild) + countNode0(T->rchild);
}
9、度为1的结点个数
//统计度为1的结点个数
int countNode1(BiTree T)
{
if (T == NULL)//结点空返回0递归结束
return 0;
//度为1的结点的左右孩子只存在其中任意一个
else if ((T->lchild == NULL && T->rchild != NULL) || (T->lchild != NULL && T->rchild == NULL))
return 1 + countNode1(T->lchild) + countNode1(T->rchild);
else
return countNode1(T->lchild) + countNode1(T->rchild);
}
10、度为2的结点个数
//统计度为2的结点个数
int countNode2(BiTree T)
{
if (T == NULL)
return 0;
else if (T->lchild && T->rchild)//度为2的结点的左右孩子都存在
return 1 + countNode2(T->lchild) + countNode2(T->rchild);
else
return countNode2(T->lchild) + countNode2(T->rchild);
}
11、销毁二叉树
//销毁二叉树
void destroyBiTree(BiTree& T)
{//在后序遍历基础上进行
if (T)
{
destroyBiTree(T->lchild);
destroyBiTree(T->rchild);
delete T;
}
}
五、测试用例
将上述代码封装成一个头文件 biTree.h(因为层次遍历中用到了队列,注意包含队列的头文件,主页有封装好的链队列相关头文件),在main函数里进行测试各项操作。测试中用到的二叉树如下图。
/*测试代码*/
#include<iostream>
using namespace std;
#include"biTree.h"
template<typename T>
void visit(T e)//遍历过程用到的函数,仅做打印操作
{
cout << e << "->";
}
void testBiTree()
{
BiTree T;
cout << "请输入某棵二叉树的先序遍历序列:";
createBiTree(T);//ABD##E##CF##G###
cout << endl;
printf("先序遍历:");
preOrderTraverse(T, visit);
printf("^\n");
printf("中序遍历:");
inOrderTraverse(T, visit);
printf("^\n");
printf("后序遍历:");
postOrderTraverse(T, visit);
printf("^\n");
printf("层次遍历:");
levelTraverse(T, visit);
printf("^\n");
printf("深度:%d\n", depth(T));
cout << "结点总数:" << countNode(T) << endl;
cout << "度为0:" << countNode0(T) << endl;
cout << "度为1:" << countNode1(T) << endl;
cout << "度为2:" << countNode2(T) << endl;
destroyBiTree(T);
}
int main()
{
testBiTree();
return 0;
}