一、树(树的定义是采用递归方法)
(一)、
1.树:n(n≥0)个结点的有限集合。 当n=0时,称为空树; 任意一棵非空树满足以下条件: ⑴ 有且仅有一个特定的称为根的结点; ⑵ 当n>1时,除根结点之外的其余结点被分成m(m>0)个互不相交的有限集合T1,T2,… ,Tm,其中每个集合又是一棵树,并称为这个根结点的子树。
1)结点的度:结点所拥有的子树的个数。
2)树的度:树中各结点度的最大值。
3)叶子结点:度为0的结点,也称为终端结点。
4)分支结点:度不为0的结点,也称为非终端结点。
5)孩子、双亲:树中某结点子树的根结点称为这个结点的孩子结点,这个结点称为它孩子结点的双亲结点;
6)兄弟:具有同一个双亲的孩子结点互称为兄弟。
7)路径:如果树的结点序列n1, n2, …, nk有如下关系:结点ni是ni+1的双亲(1<=i<k),则把n1, n2, …, nk称为一条由n1至nk的路径;路径上经过的边的个数称为路径长度。
8)祖先、子孙:在树中,如果有一条路径从结点x到结点y,那么x就称为y的祖先,而y称为x的子孙。
9)结点所在层数:根结点的层数为1;对其余任何结点,若某结点在第k层,则其孩子结点在第k+1层。 树的深度:树中所有结点的最大层数,也称高度。
10)层序编号:将树中结点按照从上层到下层、同层从左到右的次序依次给他们编以从1开始的连续自然数。
11)有序树、无序树:如果一棵树中结点的各子树从左到右是有次序的,称这棵树为有序树;反之,称为无序树。
12)森林:m (m≥0)棵互不相交的树的集合。
13)同构:对两棵树,若通过对结点适当地重命名,就可以使这两棵树完全相等(结点对应相等,结点对应关系也相等),则称这两棵树同构。
(二)树的抽象数据类型定义
ADT Tree Data
树是由一个根结点和若干棵子树构成, 树中结点具有相同数据类型及层次关系
Operation:
1)InitTree :前置条件:树不存在;输入:无;功能:初始化一棵树;输出:无 ;后置条件:构造一个空树。
2 )DestroyTree:前置条件:树已存在;输入:无;功能:销毁一棵树;输出:无 ;后置条件:释放该树占用的存储空间。
3)Root:前置条件:树已存在;输入:无;功能:求树的根结点;输出:树的根结点的信息; 后置条件:树保持不变。
4)Parent:前置条件:树已存在;输入:结点x ; 功能:求结点x的双亲;输出:结点x的双亲的信息;后置条件:树保持不变
5)Depth:前置条件:树已存在;输入:无;功能:求树的深度;输出:树的深度;后置条件:树保持不变
6)PreOrder:前置条件:树已存在;输入:无;功能:前序遍历树 ;输出:树的前序遍历序列;后置条件:树保持不变
7) PostOrder:前置条件:树已存在;输入:无;功能:后序遍历树;输出:树的后序遍历序列;后置条件:树保持不变
endADT
(三)树的遍历操作
树的遍历:从根结点出发,按照某种次序访问树中所有结点,使得每个结点被访问一次且仅被访问一次。
访问:抽象操作,可以是对结点进行的各种处理,这里简化为输出结点的数据。
树通常有前序(根)遍历、后序(根)遍历和层序(次)遍历三种方式。
1.前序遍历
树的前序遍历操作定义为: 若树为空,不进行遍历;否则 ⑴ 访问根结点; ⑵ 按照从左到右的顺序前序遍历根结点的每一棵子树
2.后序遍历
树的后序遍历操作定义为: 若树为空,则遍历结束;否则 ⑴ 按照从左到右的顺序后序遍历根结点的每一棵子树; ⑵ 访问根结点。
3.层序遍历
树的层序遍历操作定义为: 从树的第一层(即根结点)开始,自上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。
(四)数的存储结构
1.双亲表示法
基本思想: 用一维数组来存储树的各个结点(一般按层序存储), 数组中的一个元素对应树中的一个结点, 每个结点记录两类信息:结点的数据信息以及该结点的双亲在数组中的下标。
data:存储树中结点的数据信息; parent:存储该结点的双亲在数组中的下标。
双亲表示法中结点数据类型的定义:
template <class T>
struct PNode{
T data; //数据域
int parent; //指针域,双亲在数组中的下标
} ;
2.孩子表示法——多重链表表示法(节点中的指针域表示孩子)
链表中的每个结点包括一个数据域和多个指针域,每个指针域指向该结点的一个孩子结点。
方案一:指针域的个数等于树的度
其中:data:数据域,存放该结点的数据信息;child1~childd:指针域,指向该结点的孩子。
方案二: 指针域的个数等于该结点的度
其中:data:数据域,存放该结点的数据信息;degree:度域,存放该结点的度;child1~childd:指针域,指向该结点的孩子。
3.孩子兄弟表示法(又称为二叉链表表示法)
某结点的第一个孩子是惟一的 某结点的右兄弟是惟一的,设置两个分别指向该结点的第一个孩子和右兄弟的指针。
data:数据域,存储该结点的数据信息; firstchild:指针域,指向该结点第一个孩子; rightsib:指针域,指向该结点的右兄弟结点。
template <class T>
struct TNode{
T data;
TNode <T> *firstchild, *rightsib;
};
二、二叉树的逻辑结构
二叉树的定义:
二叉树是n(n≥0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成
1.二叉树的特点
⑴ 每个结点最多有两棵子树; ⑵ 二叉树是有序的,其次序不能任意颠倒。
2.特殊的二叉树
(1)斜树
1 )所有结点都只有左子树的二叉树称为左斜树;
2 )所有结点都只有右子树的二叉树称为右斜树;
3)左斜树和右斜树统称为斜树。
特点: 在斜树中,每一层只有一个结点; 斜树的结点个数与其深度相同。
(2)满二叉树
在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上。
特点:叶子只能出现在最下一层; 只有度为0和度为2的结点。
(3)完全二叉树
对一棵具有n个结点的二叉树按层序编号,如果编号为i(1≤i≤n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中的位置完全相同。
在满二叉树中,从最后一个结点开始,连续去掉任意个结点,即是一棵完全二叉树。
特点:1. 叶子结点只能出现在最下两层,且最下层的叶子结点都集中在二叉树的左部; 2. 完全二叉树中如果有度为1的结点,只可能有一个,且该结点只有左孩子。 3. 深度为k的完全二叉树在k-1层上一定是满二叉树。
3.二叉树的基本性质
1)二叉树的第i层上最多有2i-1个结点(i≥1)。
2)一棵深度为k的二叉树中,最多有2k-1个结点,最少有k个结点。
3)在一棵二叉树中,如果叶子结点数为n0,度为2的结点数为n2,则有: n0=n2+1。
4)具有n个结点的完全二叉树的深度为 log2n +1。
5)对一棵具有n个结点的完全二叉树中从1开始按层序编号,则对于任意的序号为i(1≤i≤n)的结点(简称为结点i),有: (1)如果i>1, 则结点i的双亲结点的序号为 i/2;如果i=1, 则结点i是根结点,无双亲结点。 (2)如果2i≤n, 则结点i的左孩子的序号为2i; 如果2i>n,则结点i无左孩子。 (3)如果2i+1≤n, 则结点i的右孩子的序号为2i+1;如果2i+1>n,则结点 i无右孩子。
4.二叉树的抽象数据类型定义
ADT BiTree
Data
由一个根结点和两棵互不相交的左右子树构成,
结点具有相同数据类型及层次关系
Operation
InitBiTree
前置条件:无
输入:无
功能:初始化一棵二叉树
输出:无
后置条件:构造一个空的二叉树
DestroyBiTree
前置条件:二叉树已存在
输入:无
功能:销毁一棵二叉树
输出:无
后置条件:释放二叉树占用的存储空间
InsertL
前置条件:二叉树已存在
输入:数据值x,指针parent
功能:将数据域为x的结点插入到二叉树中,作为结点parent的左孩子。如果结点parent原来有左孩子,则将结点parent原来的左孩子作为结点x的左孩子
输出:无
后置条件:如果插入成功,得到一个新的二叉树
DeleteL
前置条件:二叉树已存在
输入:指针parent
功能:在二叉树中删除结点parent的左子树
输出:无
后置条件:如果删除成功,得到一个新的二叉树
Search
前置条件:二叉树已存在
输入:数据值x
功能:在二叉树中查找数据元素x
输出:指向该元素结点的指针
后置条件:二叉树不变
PreOrder
前置条件:二叉树已存在
输入:无
功能:前序遍历二叉树
输出:二叉树中结点的一个线性排列
后置条件:二叉树不变
InOrder
前置条件:二叉树已存在
输入:无
功能:中序遍历二叉树
输出:二叉树中结点的一个线性排列
后置条件:二叉树不变
PostOrder
前置条件:二叉树已存在
输入:无
功能:后序遍历二叉树
输出:二叉树中结点的一个线性排列
后置条件:二叉树不变
LeverOrder
前置条件:二叉树已存在
输入:无
功能:层序遍历二叉树
输出:二叉树中结点的一个线性排列
后置条件:二叉树不变
endADT
5.二叉树的遍历操作
二叉树的遍历是指从根结点出发,按照某种次序访问二叉树中的所有结点,使得每个结点被访问一次且仅被访问一次。
(前序遍历 中序遍历 后序遍历 层序遍历)
1)前序(根)遍历:
若二叉树为空,则空操作返回;否则: ①访问根结点; ②前序遍历根结点的左子树; ③前序遍历根结点的右子树。
2)中序(根)遍历 :
若二叉树为空,则空操作返回;否则: ①中序遍历根结点的左子树; ②访问根结点; ③中序遍历根结点的右子树。
3)后序(根)遍历:
若二叉树为空,则空操作返回;否则: ①后序遍历根结点的左子树; ②后序遍历根结点的右子树。 ③访问根结点;
4)层序遍历:二叉树的层次遍历是指从二叉树的第一层(即根结点)开始,从上至下逐层遍历,在同一层中,则按从左到右的顺序对结点逐个访问。
6.二叉树的存储结构及实现
1)顺序存储结构
二叉树的顺序存储结构就是用一维数组存储二叉树中的结点,并且结点的存储位置(下标)应能体现结点之间的逻辑关系——父子关系。
如何利用数组下标来反映结点之间的逻辑关系?
完全二叉树和满二叉树中结点的序号可以唯一地反映出结点之间的逻辑关系 。
(二叉树的顺序存储结构一般仅存储完全二叉树)
2)二叉链表
基本思想:令二叉树的每个结点对应一个链表结点,链表结点除了存放与二叉树结点有关的数据信息外,还要设置指示左右孩子的指针。
其中,data:数据域,存放该结点的数据信息; lchild:左指针域,存放指向左孩子的指针; rchild:右指针域,存放指向右孩子的指针。
template <class T>
struct BiNode
{
T data;
BiNode<T> *lchild, *rchild;
};
二叉链表存储结构的类声明
template <class T>
class BiTree
{
public:
BiTree();
~BiTree( );
void PreOrder(){PreOrder(root);}
void InOrder() {InOrder(root);}
void PostOrder() {PostOrder(root);}
void LevelOrder(){LeverOrder(root)};
private:
BiNode<T> *root;
BiNode<T> * Creat( );
void Release(BiNode<T> *root);
void PreOrder(BiNode<T> *root);
void InOrder(BiNode<T> *root);
void PostOrder(BiNode<T> *root);
void LevelOrder(BiNode<T> *root);
};
前序遍历——递归算法:
template <class T>
void BiTree::PreOrder(BiNode<T> *root)
{
if (root ==NULL) return;
else {
cout<<root->data;
PreOrder(root->lchild);
PreOrder(root->rchild);
}
}
前序遍历——非递归算法:
template <class T>
void BiTree::PreOrder(BiNode<T> *root) {
SeqStack<BiNode<T> *> s;
while (root!=NULL | | !s.empty()) {
while (root!= NULL) {
cout<<root->data;
s.push=root;
root=root->lchild;
}
if (!s.empty()) {
root=s.pop();
root=root->rchild;
}
}
}
中序遍历——递归算法:
template <class T>
void BiTree::InOrder (BiNode<T> *root)
{
if (root==NULL) return;
else {
InOrder(root->lchild);
cout<<root->data;
InOrder(root->rchild);
}
}
中序遍历——非递归算法:
template <class T>
void BiTree::InOrderwithoutD (BiNode<T> *root)
{
stack< BiNode<T> * > aStack;
while(!aStack.empty()||root) {
while(root){
aStack.push(root);
root=root->lchild;
}
if(!aStack.empty()){
root=aStack.top();
aStack.pop();
cout<<root->data;
root=root->rchild;
}
}
}
后序遍历——递归算法:
template <class T>
void BiTree::PostOrder(BiNode<T> *root)
{
if (root==NULL) return;
else {
PostOrder(root->lchild);
PostOrder(root->rchild);
cout<<root->data;
}
}
非递归后序遍历:
栈中的元素类型定义StackElement
enum Tags{Left,Right}; //特征标识定义
template <class T>
class StackElement //栈元素的定义
{
public:
BiTreeNode<T>* pointer; //指向二叉树结点的指针
Tags tag; //特征标识申明
};
三、树、森林与二叉树的转换
1.森林转换为二叉树森林转换为二叉树
⑴ 将森林中的每棵树转换成二叉树; ⑵ 从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树根结点的右孩子,当所有二叉树连起来后,此时所得到的二叉树就是由森林转换得到的二叉树。
2.二叉树转换为树或森林
⑴ 加线——若某结点x是其双亲y的左孩子,则把结点x的右孩子、右孩子的右孩子、……,都与结点y用线连起来; ⑵ 去线——删去原二叉树中所有的双亲结点与右孩子结点的连线; ⑶ 层次调整——整理由⑴、⑵两步所得到的树或森林,使之层次分明。
森林的遍历:
森林有两种遍历方法: ⑴前序(根)遍历:前序遍历森林即为前序遍历森林中的每一棵树。 ⑵后序(根)遍历:后序遍历森林即为后序遍历森林中的每一棵树。
四、最优二叉树-哈夫曼树及哈夫曼编码
叶子结点的权值:对叶子结点赋予的一个有意义的数值量。
二叉树的带权路径长度:设二叉树具有n个带权值的叶子结点,从根结点到各个叶子结点的路径长度与相应叶子结点权值的乘积之和。
哈夫曼树:给定一组具有确定权值的叶子结点,带权路径长度最小的二叉树。
哈夫曼树的特点: 1. 权值越大的叶子结点越靠近根结点,而权值越小的叶子结点越远离根结点。 2. 只有度为0(叶子结点)和度为2(分支结点)的结点,不存在度为1的结点。
哈夫曼算法基本思想: ⑴ 初始化:由给定的n个权值{w1,w2,…,wn}构造n棵只有一个根结点的二叉树,从而得到一个二叉树集合F={T1,T2,…,Tn}; ⑵ 选取与合并:在F中选取根结点的权值最小的两棵二叉树分别作为左、右子树构造一棵新的二叉树,这棵新二叉树的根结点的权值为其左、右子树根结点的权值之和; ⑶ 删除与加入:在F中删除作为左、右子树的两棵二叉树,并将新建立的二叉树加入到F中; ⑷ 重复⑵、⑶两步,当集合F中只剩下一棵二叉树时,这棵二叉树便是哈夫曼树。
伪代码:
1. 数组huffTree初始化,所有元素结点的双亲、左
右孩子都置为-1;
2. 数组huffTree的前n个元素的权值置给定值w[n];
3. 进行n-1次合并
3.1 在二叉树集合中选取两个权值最小的根结点,
其下标分别为i1, i2;
3.2 将二叉树i1、i2合并为一棵新的二叉树k(初值为n;依次递增);
哈夫曼编码算法的实现:
从叶子结点到根, 逆向求每个叶子结点对应的哈夫曼编码 根据huffman树中叶子节点的个数,构造一个字符串数组,每个数组分量是一个字符串,用于存放该节点对应的huffman编码。
五、线索二叉树的存储结构:线索链表
enum flag {Child, Thread};
template <class T>
struct ThrNode
{
T data;
ThrNode<T> *lchild, *rchild;
flag ltag, rtag;
};
二叉树的遍历方式有4种,故有4种意义下的前驱和后继,相应的有4种线索二叉树: ⑴ 前序线索二叉树 ⑵ 中序线索二叉树 ⑶ 后序线索二叉树 ⑷ 层序线索二叉树
建立带有标志位的二叉链树:
template <class T>ThrNode<T>* InThrBiTree<T>::Creat( ){
ThrNode<T> *root;
T ch;
cout<<"请输入创建一棵二叉树的结点数据"<<endl;
cin>>ch;
if (ch=="#") root = NULL;
else{
root=new ThrNode<T>;
root->data = ch;
root->ltag = Child; root->rtag = Child;
root->lchild = Creat( );
root->rchild = Creat( );
}
return root;
}
中序线索化二叉树——递归实现 :
template <class T> void ThrBiTree<T>::ThrBiTree (ThrNode<T>*root) {
if (root==NULL) return; //递归结束条件
ThrBiTree(root->lchild);
if (!root->lchild){ //对root的左指针进行处理
root->ltag = Thread;
root->lchild = pre; //设置pre的前驱线索
}
if (!root->rchild) root->rtag = Thread;
if(pre != NULL){
if (pre->rtag==Thread) pre->rchild = root;
}
pre = root;
ThrBiTree(root->rchild);
}
线索二叉树的建立:
ThrNode<T>* pre = NULL
template <class T>
InThrBiTree<T>::InThrBiTree( )
{
//ThrNode<T>* pre = NULL;
this->root = Creat( );
ThrBiTree(root);
}
线索链表的遍历算法——中序遍历中序线索树:
template <class T>
void InThrBiTree<T>::InOrder(ThrNode<T> *root){
ThrNode<T>* p = root;
if (root==NULL) return;
while (p->ltag==Child) { p = p->lchild; }
cout<<p->data<<" ";
while (p->rchild!=NULL) {
p = Next(p);
cout<<p->data<<" ";
}
cout<<endl;
}