目录
- 树
-
- 1、树的概念
-
- 1.1 树的逻辑结构和基本运算
-
- 1.1.1 树的定义
- 1.1.2 树的常见基本操作
- 1.2 树的物理结构
- 2、二叉树
-
- 2.1 二叉树的概念
-
- 2.1.1 二叉树的定义
- 2.1.2 二叉树的基本形态
-
- 2.1.2.1 基本单元
- 2.1.2.2 满二叉树
- 2.1.2.3 完全二叉树
- 2.2 二叉树的性质
- 2.3 二叉树的基本运算
- 2.4 二叉树的存储实现
-
- 2.4.1 二叉树的顺序结构
- 2.4.2 二叉树的链式结构
- 2.4.3 二叉树类引入
- 2.5 二叉树类的实现
-
- 2.5.1 创建一棵树
- 2.5.2 二叉树的遍历
-
- 2.5.2.1 前序遍历
- 2.5.2.2 中序遍历
- 2.5.2.2 后序遍历
- 2.5.3 求规模操作的实现(递归)
- 2.5.4 求高度操作的实现(递归)
- 2.5.5 删除二叉树操作的实现(递归)
- 2.6 二叉树遍历的非递归实现
-
- 2.6.1 层次遍历的非递归实现
- 2.6.2 前序遍历的非递归实现
- 2.6.3 中序遍历的非递归实现
- 2.6.4 后序遍历的非递归实现
- 2.7 根据遍历序列确定二叉树
- 3. 代码测试
-
- 全代码总结
- 4. 二叉树的应用——树和森林的表示
-
- 4.1 表示方法
- 4.2 树与二叉树的联系
- 4.3 森林与树的联系
- 注意事项
树
1、树的概念
1.1 树的逻辑结构和基本运算
1.1.1 树的定义
树是n(n>0)个节点的有限集合T,并且满足:
1)有一个被称为根(root)的节点
2) 如果有其他节点,则可分为若干个互不相交的子集。每个子集又是一棵树,且称为根节点的子树。子树的根是树根的子节点。
层次特点: 向上一一对应,向下一对多
1.1.2 树的常见基本操作
- 建树create():创建一棵空树;
- 构建一棵树MakeTree(x,TL, TR):构建一棵以x为根结点, 以TL, TR为左右子树的二叉树;
- 判空IsEmpty():判别是否为空树;
- 找根结点root():找出树的根结点。如果树是空树,则返回一个特殊的标记;
- 找父结点parent(x):找出结点x的父结点;
- 找子结点child(x,i):找结点x的第i个子结点;
- 剪枝delete(x,i):删除结点x的第i棵子树;
- 清空clear():删除树中的所有结点;
- 遍历traverse():访问树上的每一个结点
1.2 树的物理结构
两种结构:
- 顺序结构: 数组是线性的,而树的关系是非线性的
- 链式结构: 并没有像链表一样每个节点有固定的格式
树比较难实现构建,所以我们先将其放一放,转而研究工具性结构——二叉树。
2、二叉树
2.1 二叉树的概念
2.1.1 二叉树的定义
二叉树(Binary Tree) 是节点的有限集合。与树不同,它在现实生活并没有很广泛的对应关系,偏向工具性。
或者为空,或者由一个根节点及两棵互不相交的左、右子树构成,其左右子树又都是二叉树。
注 意 \color{red}{注意} 注意:二叉树必须严格区分左右子树。
2.1.2 二叉树的基本形态
2.1.2.1 基本单元
(需注意其中的d和e并不相同)
从中也可以看出,二叉树和树并不相同(树并不会区分左右):
2.1.2.2 满二叉树
一棵二叉树中任意一层的节点个数都达到了最大值(高度为k,则节点个数为 1 + 2 + 2 2 + 2 3 + ⋯ + 2 k − 1 = 2 k − 1 1+2+2^2 + 2^3+ \cdots+2^{k-1}=2^k-1 1+2+22+23+⋯+2k−1=2k−1)
2.1.2.3 完全二叉树
在满二叉树的最底层自右向左依次去掉若干个节点
(自右向左使得二叉树具有许多好的性质,将会在下面进行介绍)
特点:
- 所有的叶节点都出现在最低的两层
- 对任一节点,如果其右子树的高度为k,则其左子树的高度为k或k+1
2.2 二叉树的性质
- 性质一: 一棵非空二叉树的第i层最多有 2 i − 1 2^{i-1} 2i−1个节点( i ≥ 1 i \geq 1 i≥1)
(可用数学归纳法进行证明) - 性质二:一棵高度为k的二叉树,最多具有 2 k − 1 2^k-1 2k−1个节点
- 性质三: 对于一棵非空二叉树,如果度数为0的节点数(叶子结点数)为 n 0 n_0 n0,度数为2的节点数为 n 2 n_2 n2,则有: n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1成立。
证 明 \color{red}{证明} 证明:
设度数为0的节点数为 n 0 n_0 n0,度数为1的节点数为 n 1 n_1 n1,度数为2的节点数为 n 2 n_2 n2,则总节点数 n = n 0 + n 1 + n 2 n=n_0+n_1+n_2 n=n0+n1+n2 。
从下往往看:和父亲有联系的节点个数(体现在图中即线条个数)为 n − 1 n-1 n−1
从上往下看:线条个数可以用度来表示为 0 × n 0 + 1 × n 1 + 2 × n 2 0 \times n_0+1 \times n_1+2\times n_2 0×n0+1×n1+2×n2
由两者相等,即可推出: n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1 - 性质四:具有n个节点的完全二叉树的高度 k = ⌊ log 2 n ⌋ + 1 k=\lfloor \log_2n \rfloor +1 k=⌊log2n⌋+1(其中 ⌊ x ⌋ \lfloor x \rfloor ⌊x⌋表示x向下取整)
证 明 \color{red}{证明} 证明:
由 2 k − 1 ≤ n < 2 k 2^{k-1} \leq n <2^k 2k−1≤n<2k 可得: k − 1 ≤ log 2 n < k k-1 \leq \log_2n <k k−1≤log2n<k ,由于k是整数,所以有: k = ⌊ log 2 n ⌋ + 1 k=\lfloor \log_2n \rfloor +1 k=⌊log2n⌋+1 - 性质五: 如果对一棵具有n个节点的完全二叉树中的结点按层自上而下,每一层按自左向右依次编号。若设根节点的编号为1,则对任意编号为i的结点( 1 ≤ i ≤ n 1 \leq i \leq n 1≤i≤n),有:
1)如果i=1,则该结点是二叉树的根节点
2)如果i>1,则其父亲节点的编号为 ⌊ i / 2 ⌋ \lfloor i/2 \rfloor ⌊i/2⌋
3)如果2i>n,则编号为i的结点为叶子节点,没有儿子,否则,其左儿子的结点编号为2i
4)如果2i+1>n,则编号为i的结点无右儿子,否则其右儿子的编号为2i+1
2.3 二叉树的基本运算
- 建树create():创建一棵空的二叉树;
- createTree(const elemType &flag);//创建一棵非空二叉树
- 判空IsEmpty():判别二叉树是否为空树;
- 找根结点root():找出二叉树的根结点。
- 找父结点parent(x):找出结点x的父结点;
- 找左孩子lchild(x):找结点x的左孩子结点;
- 找右孩子rchild(x):找结点x的右孩子结点;
- 求高度height():计算二叉树高度;
- 求结点个数size():计算二叉树中有多少结点;
- 删除左子树delLeft(x):删除结点x的左子树;
- 删除右子树delRight(x):删除结点x的右子树;
- 清空clear():删除二叉树中的所有结点;
- 遍历traverse():访问二叉树上的每一个结点。
2.4 二叉树的存储实现
2.4.1 二叉树的顺序结构
完全二叉树的顺序存储
根据2.2 的编号性质,可以省略左右孩子字段(编号为偶数,则为左孩子)
普通二叉树的顺序存储
将普通的树修补成完全二叉树
顺序存储的特点:
- 存储空间的浪费
- 一般只用于一些特殊的场合,如静态的并且节点个数已知的完全二叉树或接近完全二叉树的二叉树。
2.4.2 二叉树的链式结构
下为利用二叉树链式存储的标准形式进行存储的实例:
2.4.3 二叉树类引入
- 二叉树的顺序实现只适用于一些特殊的情况
- 三叉树有冗余,大多数情况下,二叉树都输用二叉链表实现。
二叉树类的定义
//BTree类的前向说明
template<class elemType>
class BTree;
template<class elemType>
class Node {
friend class BTree<elemType>;//声明BTree类为友元
private:
elemType data;
Node* left;
Node* right;
public:
Node() {
left = NULL;
right = NULL;
}
Node(const elemType& e, Node* L = NULL, Node* R = NULL) {
data = e;
left = L;
right = R;
}
};
template<class elemType>
class BTree {
private:
Node<elemType>* root;
//elemType stopFlag;
int Size(Node<elemType>* t);//求以t为根的二叉树的结点个数
int Height(Node<elemType>* t); //求以t为根的二叉树的高度。
void DelTree(Node<elemType>* t);//删除以t为根的二叉树
void PreOrder(Node<elemType>* t);
// 按前序遍历输出以t为根的二叉树的结点的数据值
void InOrder(Node<elemType>* t);
// 按中序遍历输出以t为根的二叉树的结点的数据值
void PostOrder(Node<elemType>* t);
// 按后序遍历输出以t为根的二叉树的结点的数据值
public:
BTree() { root = NULL; }
void createTree(const elemType& flag);//创建一棵二叉树
bool isEmpty() { return (root == NULL); }// 判断叉树是否为空
Node<elemType>* GetRoot() { return root; }
int Size(); //求二叉树的结点个数。
int Height(); //求二叉树的高度。
void DelTree();//删除二叉树
void PreOrder();// 按前序遍历输出二叉树的结点的数据值
void InOrder();// 按中序遍历输出二叉树的结点的数据值
void PostOrder();// 按后序遍历输出二叉树的结点的数据值
void LevelOrder();// 按中序遍历输出二叉树的结点的数据值
};
2.5 二叉树类的实现
2.5.1 创建一棵树
思路:
- 先输入根节点的值,创建根节点
- 对已添加到树上的每个节点,依次输入他的两个儿子的值。如果没有儿子,则输入一个特定值
算法实现:
- 建立队列,队列中元素为结点地址
- 主动输入根结点值,建结点,进队
- 逐步出队,与左右孩子链接,左右孩子的结点进队
- 反复进行3,直到队空
实现代码:
template<class elemType>
void BTree<elemType>::createTree(const elemType& flag) {
//因为队列元素为结点地址,即为Node*
seqQueue<Node<elemType>*> que;
elemType x;
Node<elemType>* p;
elemType chL, chR;
Node<elemType>* LP, *RP;
cout << "Please input the value of the root:" << endl;
cin >> x;
if (x == flag) return;
else root = new Node<elemType>(x);
que.enQueue(root);//root地址进队
//如果队列不为空,则出队
while (!que.isEmpty()) {
p = que.deQueue();
cout << "please input the children of " << p->data << ":" << endl;
cin >> chL >> chR;
if (chL != flag) {//该结点有左孩子
LP = new Node<elemType>(chL);//new结点
p->left = LP;//链到父亲节点
que.enQueue(LP);//当前节点地址进队
}
if (chR != flag) {//该结点有右孩子
RP = new Node<elemType>(chR);
p->right = RP;
que.enQueue(RP);
}
}
}
执行示例:
2.5.2 二叉树的遍历
在遍历中逐渐体会 “递归是树的灵魂” 这句话
遍历方式:
- 层次遍历:层次自上而下,每一层从左到右访问节点
- 前序遍历
- 中序遍历
- 后序遍历
2.5.2.1 前序遍历
- 如果二叉树为空,则操作为空
- 否则:
a. 访问根节点
b. 前序遍历左子树
c. 前序遍历右子树
具体例子如下:
图示介绍:
//前序遍历的实现
template<class elemType>
void BTree<elemType>::PreOrder() {
PreOrder(root);
}
template<class elemType>
void BTree<elemType>::PreOrder(Node<elemType>* t) {
if (!t) return;
cout << t->data; //访问根节点
PreOrder(t->left);
PreOrder(t->right);
}
2.5.2.2 中序遍历
- 如果二叉树为空,则操作为空
- 否则:
a. 访问根节点
b. 中序遍历左子树
c. 中序遍历右子树
具体例子如下:
//中序遍历的实现
template<class elemType>
void BTree<elemType>::InOrder() {
InOrder(root);
}
template<class elemType>
void BTree<elemType>::InOrder(Node<elemType>* t) {
if (!t) return;
InOrder(t->left);
cout << t->data; //访问根节点
InOrder(t->right);
}
2.5.2.2 后序遍历
- 如果二叉树为空,则操作为空
- 否则:
a. 访问根节点
b. 后序遍历左子树
c. 后序遍历右子树
具体例子如下:
//后序遍历的实现
template<class elemType>
void BTree<elemType>::PostOrder() {
PostOrder(root);
}
template<class elemType>
void BTree<elemType>::PostOrder(Node<elemType>* t) {
if (!t) return;
PostOrder(t->left);
PostOrder(t->right);
cout << t->data; //访问根节点
}
2.5.3 求规模操作的实现(递归)
递归的使用条件:
- 有参数体现规模的变化
- 有终止条件
数的规模=左子树的规模+右子树的规模+1(根)
//求规模
template<class elemType>
int BTree<elemType>::Size() {
return Size(root);//要实现递归,就需要传入可以变化的参数
}
template<class elemType>
int BTree<elemType>::Size(Node<elemType>* t) {
if (!t) return 0;//递归终止条件 t=NULL
return 1 + Size(t->left) + Size(t->right);
}
2.5.4 求高度操作的实现(递归)
数的高度: 1 + max ( 左 子 树 高 度 , 右 子 树 高 度 ) 1+\max(左子树高度,右子树高度) 1+max(左子树高度,右子树高度)
//求高度
template <class elemType>
int BTree<elemType>::Height() {
return Height(root);
}
template <class elemType>
int BTree<elemType>::Height(Node<elemType>* t)
//得到以t为根二叉树的高度,递归算法实现。
{
int hl, hr;
if (!t) return 0;//如果t为空,递归终止
hl = Height(t->left);
hr = Height(t->right);
if (hl >= hr) return 1 + hl;
return 1 + hr;
}
2.5.5 删除二叉树操作的实现(递归)
先清除左右子树,再删除根节点
//删除树
template <class elemType>
void BTree<elemType>::DelTree()
{
DelTree(root);
root = NULL;
}
template <class elemType>
void BTree<elemType>::DelTree(Node<elemType>* t)
//删除以t为根的二叉树,递归算法实现
{
if (!t) return;
DelTree(t->left); DelTree(t->right);
delete t;//删除根节点
}
2.6 二叉树遍历的非递归实现
递归是程序设计中强有力的工具,计算思维中核心思维之一,结构清晰、明了、美观,但是它的时间、空间效率比较低,所以在实际使用中,尝尝希望使用它的非递归版本。
下面这些不太好用语言描述具体过程,但是可以看懂代码的话还是比较好理解的,可以按着代码的程序走一遍(确定是对的)
2.6.1 层次遍历的非递归实现
利用队列实现层次遍历
//层次遍历的非递归实现
template<class elemType>
void BTree<elemType>::LevelOrder() {
seqQueue<Node<elemType>*>que(10);
Node<elemType>* p;
if (!root) return;//二叉树为空
que.enQueue(root);
while (!que.isEmpty()) {
p = que.getHead();
que.deQueue();
cout << p->data;
if (p->left) que.enQueue(p->left);
if (p->right) que.enQueue(p->right);
}
cout << endl;
}
2.6.2 前序遍历的非递归实现
前序遍历: 先访问根,然后访问左子和右子
可以设置一个栈,保存节点的地址
把根节点地址压栈。重复执行下述过程,直至栈空:
- 从栈中弹出一个节点的地址,输出该结点的值
- 如果有右子,右子地址入栈
- 如果有左子,左子地址入栈
//前序遍历的非递归实现
//利用非递归的形式,可以求得许多性质,例如规模、检查某个值是否存在等等
template<class elemType>
void BTree<elemType>::PreOrder() {
if (!root) return;
Node<elemType>* p;
seqStack<Node<elemType>*> s;
s.push(root);
while (!s.isEmpty()) {
p = s.pop();
cout << p->data;
if (p->right) s.push(p->right);
if (p->left) s.push(p->left);
}
}
2.6.3 中序遍历的非递归实现
把根节点地址压栈。重复执行下述过程,直至栈空:
- 如果节点有左子,则一直压栈
- 从栈中弹出一个节点的地址,如果有右子,右子压栈
- 如果右子还有左子,则一直压栈
//中序遍历的非递归实现
template<class elemType>
void BTree<elemType>::InOrder() {
if (!root) return;
seqStack<Node<elemType>*> s(10);
Node<elemType>* p;
p = root;
s.push(root);
while (p->left) {//压一路左子
s.push(p->left);
p = p->left;
}
while (!s.isEmpty()) {//如果栈不空
p = s.pop();//弹出并访问
cout << p->data;
if (p->right) {//如果右子存在
s.push(p->right);
p = p->right;
while (p->left) {//如果右子有左子则一路压下去
s.push(p->left);
p = p->left;
}
}
}
}
2.6.4 后序遍历的非递归实现
01状态,用1表示已经访问过该结点的左右子
创建两个栈,把根节点地址压入栈1,访问状态(0表示0次访问,1表示一次访问)压入栈2。重复执行下述过程,直至栈空:
- 如果状态为1,则说明已经“照顾”过其左右子,则输出
- 如果状态为0,则访问对应节点的右子树和左子树,并将当前节点的状态设为1,左右子树设为0
//后序遍历的非递归实现_01状态
template<class elemType>
void BTree<elemType>::PostOrder() {
seqStack<Node<elemType>*> s1;
seqStack<int> s2;
Node<elemType>* p;
int state;
s1.push(root);
s2.push(0);
while (!s1.isEmpty()) {//栈不空
state = s2.pop();
if (state == 1) {
p = s1.pop();
cout << p->data;
}
else {
s2.push(1);//当前节点状态由0变为1
p = s1.Top();
if (p->right) {//如果有右子,右子压栈,且状态为0
s1.push(p->right);
s2.push(0);
}
if (p->left) {//如果有左子,左子压栈,且状态为0
s1.push(p->left);
s2.push(0);
}
}
}
}
12状态,用1表示已经访问过该结点的左子,用2表示已经访问过该结点的右子
根节点入栈,把根节点地址压栈1,状态0压入栈2,重复执行下述过程,直至栈空:
- 出栈,如果对应状态为2,则输出
- 如果对应状态为1,则访问其右子树。如果有右子,则压栈(状态数为0),并将当前节点的状态数改为2
- 如果对应状态为0,则访问其左子树。如果有左子,则压栈(状态数为0),并将当前节点的状态数改为1
//后序遍历的非递归实现_12状态
template<class elemType>
void BTree<elemType>::PostOrder() {
if (!root) return;
seqStack<Node<elemType>*> s1;
seqStack<int> s2;
Node<elemType>* p;
int state;
s1.push(root);
s2.push(0);
while (!s1.isEmpty()) {
p = s1.Top();
state = s2.pop();
switch (state)
{
case 2:
s1.pop();
cout << p->data;
break;
case 1:
s2.push(2);
if (p->right) {
s1.push(p->right);
s2.push(0);
}
break;
case 0:
s2.push(1);
if (p->left) {
s1.push(p->left);
s2.push(0);
}
break;
default:
break;
}
}
}
2.7 根据遍历序列确定二叉树
直接上结论:
- 已知一个二叉树的前序+中序遍历,能唯一确定这棵二叉树
- 已知一个二叉树的后序+中序遍历,能唯一确定这棵二叉树
- 已知一个二叉树的前序+后序遍历,不能唯一确定这棵二叉树
信息互补:
前序和后序可以用来确定根节点
中序确定左右子树
例如:
算法实现:前序+中序
template<class elemType>
Node<elemType>* BTree<elemType>::buildTree(elemType pre[], int pl, int pr, elemType mid[], int ml, int mr) {
//pre数组存储前序遍历序列,pl为序列左边界下标,pr为序列右边界下标
//min数组存储中序遍历序列,ml为序列左边界下标,mr为序列右边界下标
Node<elemType>* p, * leftRoot, * rightRoot;
int i, pos, num;
int lpl, lpr, lml, lmr;//左子树中前序的左右边界、中序的左右边界
int rpl, rpr, rml, rmr;//右子树的前序的左右边界、中序的左右边界
if (pl > pr) return NULL;//递归终止条件
p = new Node<elemType>(pre[pl]);//找到子树的根并创建节点
if (!root) root = p;
//找根在中序中的位置和左子树的节点个数
for (i = ml; i <= mr; i++) {
if (mid[i] == pre[pl])
break;
}
pos = i;
num = pos - ml;//左子树中的节点个数
//找左子树的前序中序下标范围
lpl = pl + 1; lpr = pl + num;
lml = ml; lmr = pos - 1;
leftRoot = buildTree(pre, lpl, lpr, mid, lml, lmr);
//找右子树的前序中序下标范围
rpl = pl + num + 1; rpr = pr;
rml = pos + 1; rmr = mr;
rightRoot = buildTree(pre, rpl, rpr, mid, rml, rmr);
p->left = leftRoot;
p->right = rightRoot;
return p;
}
代码测试:(就不跟下面的3混了,单独测试了一下)
main.cpp
#include<iostream>
#include"seqQueue.h"
#include"seqStack.h"
#include"BTree.h"
int main() {
char pre[80];
char mid[80];
BTree<char> tree;
//tree.createTree('@');//@为结束符号
cout << "请输入前序序列:" << endl;
cin.getline(pre, 80);
cout << "请输入中序序列:" << endl;
cin.getline(mid, 80);
Node<char>*a = tree.buildTree(pre, 0, 5, mid, 0, 5);
cout << "高度为:" << tree.Height() << endl;
cout << "规模为:" << tree.Size() << endl;
cout << "前序排列为:";
tree.PreOrder();
cout << endl;
cout << "中序排列为:";
tree.InOrder();
cout << endl;
cout << "后序排列为:";
tree.PostOrder();
cout << endl;
cout << "层次排列为:";
tree.LevelOrder();
cout << endl;
return 0;
}
输出结果:
3. 代码测试
全代码总结
- 栈 seqStack.h
具体代码见:
数据结构笔记三_栈(C++)_栈的逻辑结构-优快云博客 - 队列 seqQueue.h
具体代码见:
数据结构笔记四_队列(C++)_逻辑结构 队列-优快云博客 - 二叉树 Btree.h
#ifndef Tree_H
#define Tree_H
#include<iostream>
#include"seqQueue.h"
#include"seqStack.h"
using namespace std;
//BTree类的前向说明
template<class elemType>
class BTree;
template<class elemType>
class Node {
friend class BTree<elemType>;//声明BTree类为友元
private:
elemType data;
Node* left;
Node* right;
public:
Node() {
left = NULL;
right = NULL;
}
Node(const elemType& e, Node* L = NULL, Node* R = NULL) {
data = e;
left = L;
right = R;
}
};
template<class elemType>
class BTree {
private:
Node<elemType>* root;
//elemType stopFlag;
int Size(Node<elemType>* t);//求以t为根的二叉树的结点个数
int Height(Node<elemType>* t); //求以t为根的二叉树的高度。
void DelTree(Node<elemType>* t);//删除以t为根的二叉树
void PreOrder(Node<elemType>* t);
// 按前序遍历输出以t为根的二叉树的结点的数据值
void InOrder(Node<elemType>* t);
// 按中序遍历输出以t为根的二叉树的结点的数据值
void PostOrder(Node<elemType>* t);
// 按后序遍历输出以t为根的二叉树的结点的数据值
public:
BTree() { root = NULL; }
void createTree(const elemType& flag);//创建一棵二叉树
bool isEmpty() { return (root == NULL); }// 判断叉树是否为空
Node<elemType>* GetRoot() { return root; }
int Size(); //求二叉树的结点个数。
int Height(); //求二叉树的高度。
void DelTree();//删除二叉树
void PreOrder();// 按前序遍历输出二叉树的结点的数据值
void InOrder();// 按中序遍历输出二叉树的结点的数据值
void PostOrder();// 按后序遍历输出二叉树的结点的数据值
void LevelOrder();// 按层次遍历输出二叉树的结点的数据值
};
template<class elemType>
void BTree<elemType>::createTree(const elemType& flag) {
//因为队列元素为结点地址,即为Node*
seqQueue<Node<elemType>*> que;
elemType x;
Node<elemType>* p;
elemType chL, chR;
Node<elemType>* LP, *RP;
cout << "Please input the value of the root:" << endl;
cin >> x;
if (x == flag) return;
else root = new Node<elemType>(x);
que.enQueue(root);//root地址进队
//如果队列不为空,则出队
while (!que.isEmpty()) {
p = que.deQueue();
cout << "please input the children of " << p->data << ":" << endl;
cin >> chL >> chR;
if (chL != flag) {//该结点有左孩子
LP = new Node<elemType>(chL);//new结点
p->left = LP;//链到父亲节点
que.enQueue(LP);//当前节点地址进队
}
if (chR != flag) {//该结点有右孩子
RP = new Node<elemType>(chR);
p->right = RP;
que.enQueue(RP);
}
}
}
//求规模
template<class elemType>
int BTree<elemType>::Size() {
return Size(root);//要实现递归,就需要传入可以变化的参数
}
template<class elemType>
int BTree<elemType>::Size(Node<elemType>* t) {
if (!t) return 0;//递归终止条件 t=NULL
return 1 + Size(t->left) + Size(t->right);
}
//求高度
template <class elemType>
int BTree<elemType>::Height() {
return Height(root);
}
template <class elemType>
int BTree<elemType>::Height(Node<elemType>* t)
//得到以t为根二叉树的高度,递归算法实现。
{
int hl, hr;
if (!t) return 0;//如果t为空,递归终止
hl = Height(t->left);
hr = Height(t->right);
if (hl >= hr) return 1 + hl;
return 1 + hr;
}
//删除树
template <class elemType>
void BTree<elemType>::DelTree()
{
DelTree(root);
root = NULL;
}
template <class elemType>
void BTree<elemType>::DelTree(Node<elemType>* t)
//删除以t为根的二叉树,递归算法实现
{
if (!t) return;
DelTree(t->left); DelTree(t->right);
delete t;//删除根节点
}
/*
//前序遍历的递归实现
template<class elemType>
void BTree<elemType>::PreOrder() {
PreOrder(root);
}
template<class elemType>
void BTree<elemType>::PreOrder(Node<elemType>* t) {
if (!t) return;
cout << t->data; //访问根节点
PreOrder(t->left);
PreOrder(t->right);
}
*/
//前序遍历的非递归实现
//利用非递归的形式,可以求得许多性质,例如规模、检查某个值是否存在等等
template<class elemType>
void BTree<elemType>::PreOrder() {
if (!root) return;
Node<elemType>* p;
seqStack<Node<elemType>*> s;
s.push(root);
while (!s.isEmpty()) {
p = s.pop();
cout << p->data;
if (p->right) s.push(p->right);
if (p->left) s.push(p->left);
}
}
/*
//中序遍历的递归实现
template<class elemType>
void BTree<elemType>::InOrder() {
InOrder(root);
}
template<class elemType>
void BTree<elemType>::InOrder(Node<elemType>* t) {
if (!t) return;
InOrder(t->left);
cout << t->data; //访问根节点
InOrder(t->right);
}
*/
//中序遍历的非递归实现
template<class elemType>
void BTree<elemType>::InOrder() {
if (!root) return;
seqStack<Node<elemType>*> s(10);
Node<elemType>* p;
p = root;
s.push(root);
while (p->left) {//压一路左子
s.push(p->left);
p = p->left;
}
while (!s.isEmpty()) {//如果栈不空
p = s.pop();//弹出并访问
cout << p->data;
if (p->right) {//如果右子存在
s.push(p->right);
p = p->right;
while (p->left) {//如果右子有左子则一路压下去
s.push(p->left);
p = p->left;
}
}
}
}
/*
//后序遍历的递归实现
template<class elemType>
void BTree<elemType>::PostOrder() {
PostOrder(root);
}
template<class elemType>
void BTree<elemType>::PostOrder(Node<elemType>* t) {
if (!t) return;
PostOrder(t->left);
PostOrder(t->right);
cout << t->data; //访问根节点
}
*/
/*
//后序遍历的非递归实现_01状态
template<class elemType>
void BTree<elemType>::PostOrder() {
if(!root) return;
seqStack<Node<elemType>*> s1;
seqStack<int> s2;
Node<elemType>* p;
int state;
s1.push(root);
s2.push(0);
while (!s1.isEmpty()) {//栈不空
state = s2.pop();
if (state == 1) {
p = s1.pop();
cout << p->data;
}
else {
s2.push(1);//当前节点状态由0变为1
p = s1.Top();
if (p->right) {//如果有右子,右子压栈,且状态为0
s1.push(p->right);
s2.push(0);
}
if (p->left) {//如果有左子,左子压栈,且状态为0
s1.push(p->left);
s2.push(0);
}
}
}
}
*/
//后序遍历的非递归实现_12状态
template<class elemType>
void BTree<elemType>::PostOrder() {
if (!root) return;
seqStack<Node<elemType>*> s1;
seqStack<int> s2;
Node<elemType>* p;
int state;
s1.push(root);
s2.push(0);
while (!s1.isEmpty()) {
p = s1.Top();
state = s2.pop();
switch (state)
{
case 2:
s1.pop();
cout << p->data;
break;
case 1:
s2.push(2);
if (p->right) {
s1.push(p->right);
s2.push(0);
}
break;
case 0:
s2.push(1);
if (p->left) {
s1.push(p->left);
s2.push(0);
}
break;
default:
break;
}
}
}
//层次遍历的非递归实现
template<class elemType>
void BTree<elemType>::LevelOrder() {
seqQueue<Node<elemType>*>que(10);
Node<elemType>* p;
if (!root) return;//二叉树为空
que.enQueue(root);
while (!que.isEmpty()) {
p = que.getHead();
que.deQueue();
cout << p->data;
if (p->left) que.enQueue(p->left);
if (p->right) que.enQueue(p->right);
}
cout << endl;
}
#endif
- 测试代码 main.cpp
构建的二叉树如下图:
输出结果:
4. 二叉树的应用——树和森林的表示
4.1 表示方法
树的表示:
孩子兄弟表示法——左拉儿子,右拉兄弟
森林的表示:
4.2 树与二叉树的联系
树的先根遍历:
- 如果根节点为空,遍历操作为空,否则访问根节点
- 从左到右,逐个先根遍历以根节点的孩子为根的子树
树的后根遍历
- 如果根节点为空,遍历操作为空,否则从左到右,逐个后根遍历以根节点的孩子为根的子树
- 访问根节点
联系:
!!
树的先根遍历 = 二叉树先序遍历
树的后根遍历 = 二叉树中序遍历
4.3 森林与树的联系
森林的先序遍历:
- 如果森林为空,遍历操作为空。
- 访问第一棵树的根结点。
- 从左到右逐个先序访问第一棵树中根结点的每一棵子树。
- 从左到右先序访问森林的第二棵树、第三棵树,直到所有的树。
森林的中序遍历:
- 如果森林为空,遍历操作为空。
- 从左到右逐个中序访问第一棵树中根结点的每一棵子树。
- 访问第一棵树的根结点。
- 从左到右中序访问森林的第二棵树、第三棵树,直到所有的树。
(即对每棵树进行后根遍历)
!!
森林的先序=二叉树的先序
森林的中序=二叉树的中序
注:
已知一棵树的先序遍历和后序遍历,是否能唯一确定这棵树?能!
已知一片森林的先序遍历和中序遍历,是否能唯一确定这片森林?能!
注意事项
- 在此代码中使用了多个头文件,在多个头文件放在一起时,记得加idndef结构,避免重复定义
#ifndef seqStack_H
#define seqStack_H
#endif