树
1、 树(Tree)是n(n>=0)个结点的有限集。n=0时称为空树。在任意一棵非空树中:
1) 有且仅有一个特定的称为根(Root)的结点;
2) 当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1、T2、…、Tm,其中每一个集合本身又是一棵树,并且称为根的字数(SubTree)。
2、 结点分类
1) 树的结点包含一个数据元素及若干指向其子树的分支。
2) 结点拥有的子树数称为结点的度(Degree)。度为0的结点称为叶结点(Leaf)或终端结点;度不为0的结点称为非终端结点或分支结点(内部结点)。
3) 树的度是树内各结点的度的最大值。
3、 结点间的关系
结点的子树的根称为该结点的孩子(Child),相应地,该结点称为孩子的双亲(Parent) 。同一个双亲的孩子之间直称兄弟(Sibling)。结点的祖先是从根到该结点所经分支上的所有结点。以某结点为根的子树中的任一结点都称为该结点的子孙。
4、 树的其他相关概念
Ø 结点的层次(LeveI) 从根开始定义起,根为第一层, 根的孩子为第二层。树中结点的最大层次称为树的深度(Dep也)或高度。
Ø 如果将树中结点的各子树看成从左至右是有次序的,不能互换的,则称该树为有序树,否则称为无序树。
Ø 森林(Forest) 是m(m>0) 棵互不相交的树的集合。
二 叉 树
1、二叉树(Binary Tree):n(n>=0)个结点的有限集合,该集合或者为空集(空二叉树),或者有一个根结点和两颗互不相交的、分别称为根结点的左子树和右子树的二叉树组成。
Ø 斜树:所有的结点都只有左子树的二叉树叫左斜树;所有结点都只有右子树的二叉树叫右斜树。
Ø 满二叉树:在一颗二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树成为满二叉树。
Ø 完全二叉树:对一颗具有n个结点的二叉树按层序编号,如果编号为i(1<=i<=n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这颗二叉树成为完全二叉树。
2、二叉树的性质:
1) 在二叉树的第i层至多有2的i减1次方个结点(i>=1)。
2) 深度为k的二叉树至多有2的k次方减1个结点(i>=1)。
3) 对任何一颗二叉树T,如果其终端结点数为n0,度为2的结点数位n2,则n0 = n2 + 1。
4) 具有n个结点的完全二叉树的深度为[logn]+1([x]表示不大于x的最大整数)。
5) 如果对一颗有n个结点的完全二叉树(其深度为[logn]+1)的结点按层序编号(从第1层到第[logn]+1层,每层从左到右),对任意结点i(1<=i<=n)有:
Ø 如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结点[i/2];
Ø 如果2i>n,则结点i无左孩子(结点i为叶子节点);否则其左孩子是结点2i;
Ø 如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1。
3、二叉链表
二叉树每个结点最多有两个孩子,所以为官设计一个数据域和两个指针域是比较自然的想法,我们称这样的链表叫做二叉链表。
//二叉树的二叉链表结点结构定义
typedef struct BiTNode {
ElemType data; // 结点的数据域
struct BiTNode *lchild, *rchild; // 左右孩子指针
}BiTNode,*BiTree;
void InitBiTree(BiTree *T); //构造空二叉树T
void Visit(ElemType data);//访问结点
void InsertBiTree(BiTree *T, ElemType data);//按照中序遍历为增序的原则插入结点
void CreateBiTree(BiTree *T);//按先序次序输入二叉树中结点的值
void DestroyBiTree(BiTree T);// 初始条件:二叉树T存在。操作结果:销毁二叉树T
void PreOrderTraverse(BiTree T,void(*Visit)(ElemType));//先序递归遍历T,对每个结点调用函数Visit一次且仅一次
void InOrderTraverse(BiTree T,void(*Visit)(ElemType));//中序递归遍历T
void PostOrderTraverse(BiTree T,void(*Visit)(ElemType));//后序递归遍历T
int SearchBiTree(BiTree T, int key);//在二叉树中查找指定值的元素,并返回查找次数,不存在则返回0
//构造空二叉树T
void InitBiTree(BiTree *T) { // 操作结果:构造空二叉树T
*T = NULL;
}
//访问结点:以整型格式输出
void Visit(ElemType data) {
printf("%d ",data); // 以整型格式输出
}
//按照中序遍历为增序的原则建立二叉树
void InsertBiTree(BiTree *T, ElemType data) {
if(!*T) {
*T = (BiTree)malloc(sizeof(BiTNode)); // 生成根结点
if(!*T) exit(OVERFLOW);
(*T)->data = data;
(*T)->lchild = NULL;
(*T)->rchild = NULL;
}
else if(data < (*T)->data)
InsertBiTree(&(*T)->lchild, data);
else InsertBiTree(&(*T)->rchild, data);
}
void CreateBiTree(BiTree *T) { //按先序次序输入二叉树中结点的值
InitBiTree(T);
int data;
scanf("%d",&data);
while(data != MAX) { // 输入结点的值
InsertBiTree(T, data);
scanf("%d",&data);
}
}
void DestroyBiTree(BiTree T) { // 初始条件:二叉树T存在。操作结果:销毁二叉树T
if(T) {
DestroyBiTree(T->lchild); // 递归销毁左子树,如无左子树,则不执行任何操作
DestroyBiTree(T->rchild); // 递归销毁右子树,如无右子树,则不执行任何操作
free(T); // 释放根结点 T=NULL; // 空指针赋0
}
}
int SearchBiTree(BiTree T, int key) {
searchCount++;
if(T) {
if(key == T->data)
return searchCount;
else if(key < T->data)
return SearchBiTree(T->lchild, key);
else return SearchBiTree(T->rchild, key);
}
else searchCount = 0;
return searchCount;
}
</pre></p><p></p><p class="p0" style="line-height: 200%; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-family:'Times New Roman';font-size: 10.5pt;"><span>4、</span></span><span style="font-family:'Times New Roman';font-size: 10.5pt;">二叉树的遍历</span><span style="font-family:'Times New Roman';font-size: 10.5pt;"></span></p><p class="p0" style="line-height: 200%; text-indent: 21pt; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-family:'宋体';font-size: 10.5pt;">二叉树的遍历<span style="font-family:Times New Roman;">(traversing</span></span><span style="font-family:'Times New Roman';font-size: 10.5pt;"> binary tree</span><span style="font-family:'宋体';font-size: 10.5pt;">) <span style="font-family:宋体;">是指从根结点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次。</span></span><span style="font-family:'Times New Roman';font-size: 10.5pt;"></span></p><p class="p0" style="line-height: 200%; text-indent: 21pt; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-family:'宋体';font-size: 10.5pt;">前序遍历:规则是若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树, 再前序遍历右子树。</span><span style="font-family:'Times New Roman';font-size: 10.5pt;"></span></p><p class="p0" style="line-height: 200%; text-indent: 21pt; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-family:'Times New Roman';font-size: 10.5pt;">中序遍历</span><span style="font-family:'宋体';font-size: 10.5pt;">:规则是若树为空,则空操作返回,否则从根结点开始<span style="font-family:Times New Roman;">(</span><span style="font-family:宋体;">注意并不是先访问根结点</span><span style="font-family:Times New Roman;">)</span><span style="font-family:宋体;">,中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。</span></span><span style="font-family:'Times New Roman';font-size: 10.5pt;"></span></p><p class="p0" style="line-height: 200%; text-indent: 21pt; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-family:'Times New Roman';font-size: 10.5pt;">后序遍历</span><span style="font-family:'宋体';font-size: 10.5pt;">:规则是若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点。</span><span style="font-family:'Times New Roman';font-size: 10.5pt;"></span></p><p class="p0" style="line-height: 200%; text-indent: 21pt; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-family:'Times New Roman';font-size: 10.5pt;">层序遍历</span><span style="font-family:'宋体';font-size: 10.5pt;">:规则是若树为空, 则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层中, 按从左到右的颇用才结点逐个访问。</span><pre class="objc" name="code">//递归遍历法
void PreOrderTraverse(BiTree T,void(*Visit)(ElemType)) { //先序递归遍历T,对每个结点调用函数Visit一次且仅一次
if(T) {
Visit(T->data); // 先访问根结点
PreOrderTraverse(T->lchild,Visit); // 再先序遍历左子树
PreOrderTraverse(T->rchild,Visit); // 最后先序遍历右子树
}
}
void InOrderTraverse(BiTree T,void(*Visit)(ElemType)) { //中序递归遍历T
if(T) {
InOrderTraverse(T->lchild,Visit); // 先中序遍历左子树
Visit(T->data); // 再访问根结点
InOrderTraverse(T->rchild,Visit); // 最后中序遍历右子树
}
}
void PostOrderTraverse(BiTree T,void(*Visit)(ElemType)) { //后序递归遍历T
if(T) {
PostOrderTraverse(T->lchild,Visit); // 先后序遍历左子树
PostOrderTraverse(T->rchild,Visit); // 再后序遍历右子树
Visit(T->data); // 最后访问根结点
}
}
</pre><pre class="objc" name="code">//非递归遍历
void PreOrderTraverse(BiTree *root) //非递归前序遍历
{
stack<BiTree*> s;
BiTree *p=root;
while(p!=NULL||!s.empty())
{
while(p!=NULL)
{
cout<<p->data<<" ";
s.push(p);
p=p->lchild;
}
if(!s.empty()) //p为空,此时s.top为p的父节点q,该遍历q的右子树
{
p=s.top();
s.pop();
p=p->rchild;
}
}
}
void InOrderTraverse(BiTree *root) //非递归中序遍历
{
stack<BiTree*> s;
BiTree *p=root;
while(p!=NULL||!s.empty())
{
while(p!=NULL)
{
s.push(p);
p=p->lchild;
}
if(!s.empty())
{
p=s.top();
cout<<p->data<<" ";
s.pop();
p=p->rchild;
}
}
}
void PostOrderTraverse(BiTree *root) //非递归后序遍历
{
stack<BiTNode*> s;
BiTree *p=root;
BiTNode *temp;
while(p!=NULL||!s.empty())
{
while(p!=NULL) //沿左子树一直往下搜索,直至出现没有左子树的结点
{
BiTNode *btn=(BiTNode *)malloc(sizeof(BiTNode));
btn->btnode=p;
btn->isFirst=true;
s.push(btn);
p=p->lchild;
}
if(!s.empty())
{
temp=s.top();
s.pop();
if(temp->isFirst==true) //表示是第一次出现在栈顶
{
temp->isFirst=false;
s.push(temp);
p=temp->btnode->rchild;
}
else //第二次出现在栈顶
{
cout<<temp->btnode->data<<" ";
p=NULL;
}
}
}
}
void PostOrderTraverse0(BiTree *root) //非递归后序遍历
{
stack<BiTree*> s;
BiTree *cur; //当前结点
BiTree *pre=NULL; //前一次访问的结点
s.push(root);
while(!s.empty())
{
cur=s.top();
if((cur->lchild==NULL&&cur->rchild==NULL)||
(pre!=NULL&&(pre==cur->lchild||pre==cur->rchild)))
{
cout<<cur->data<<" "; //如果当前结点没有孩子结点或者孩子节点都已被访问过
s.pop();
pre=cur;
}
else
{
if(cur->rchild!=NULL)
s.push(cur->rchild);
if(cur->lchild!=NULL)
s.push(cur->lchild);
}
}
}
3、线索二叉树
指向前驱和后继的指针称为线索,加上线索的二叉链表称为线索链衰,相应的二叉树就.称为线索二叉树(Threaded BinaryTree)。
对二叉树以某种次序遍历使其变为线索二叉树的过程称做是线索化。
线索化的实质就是将二叉链表中的空指针改为指向前驱或后继的线索。 由于前驱和后继的信息只有在遍历该二叉树时才能得到,所以线索化的过程就是在遍历的过程中修改窒指针的过程。
树、森林与二叉树的转换
1、树转换为二叉树
1) 加线。在所有兄弟结点之间加一条连线。
2) 去钱。对树中每个结点,只保留它与第一个孩子结点的连线,删除色与其他孩子结点之间的连线。
3) 层次调整。以树的根结点为轴心,将整棵树顺时针旋转一定的角度,使之结构层次分明。注意第一个孩子是二叉树结点的左孩子,兄弟转换过来的孩子是结点的右孩子。
1、森林转换为二叉树
森林是由若干棵树组成的,所以完全可以理解为,森林中的每一棵树都是兄弟,可以按照兄弟的处理办法来操作。步骤如下:
1) 把每个树转换为二叉树。
2) 第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树的根结点右孩子,用线连接起来。当所有的二叉树连接起来后就得到了由森林转换来的二叉树。
2、二叉树转换为树
二叉树转换为树是树转换为二叉树的逆过程,也就是反过来做而已。如图6-11-4所示。步骤如下:
1) 加线。若某结点的左孩子结点存在,则将这个左孩子的右孩子结点、右孩子的右孩子结点、右孩子的右孩子的右孩子结点……哈,反正就是左孩子的n 个右孩子结点都作为此结点的孩子。将该结点与这些右孩子结点用线连接起来。
2) 去钱。删除原二叉树中所有结点与其右孩子结点的连线。
3) 层次调整。使之结构层次分明。
3、二叉树转换为森林
判断一棵二叉树能够转换成一棵树还是森林,标准很简单, 那就是只要看这棵二叉树的根结点有没有右孩子,有就是森林,没有就是一棵树.那么如果是转换成森林,步骤如下:
1) 从根结点开始, 若右孩子存在,则把与右孩子结点的连线删除,再查看分离后的二叉树,若右孩子存在,则连续删除……,直到所有右孩子连线都删除为止,得到分离的二叉树。
2) 再将每棵分离后的二叉树转换为树即可。
赫夫曼树及其应用
1、 从树中一个结点到另一个结点之间的分支构成两个结点之间的路径,路径上的分支数目称做路径长度。
树的路径长度就是从树根到每一结点的路径长度之和。
带权路径长度WPL最小的二叉树称做赫夫曼树。
2、 赫夫曼树的构造
1) 根据给定的n个权值{ Wl,W2,…,Wn }构成n棵二叉树的集合F={Tl,T2,…Tn},其中每棵二叉树Ti 中只有一个带权为Wi 根结点,其左右子树均为空。
2) 在F中选取两棵根结点的权值最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树的根结点的权值为其左右子树上根结点的权值之和。
3) 在F中删除这两棵树,同时将新得到的二叉树加入F中。
重复2和3步骤,直到F只含一棵树为止。这棵树便是赫夫曼树。