您的位置:首页 > 理论基础 > 数据结构算法

数据结构笔记——第五章 树和二叉树

2014-10-31 13:48 239 查看
5.1 数的逻辑结构

5.1.1 数的定义和基本术语

1、树的定义

n(n>=0)个结点的有限集合。当n=0时,称为空树;任意非空树满足:

1)有且仅有一个特定的称为根的结点;

2)当n>1时,除根结点之外的其余结点被分成m(m>0)个互不相交的有限集合,其中每个集合是一棵树,并称为根结点的子树。

树中将数据元素称为结点。

2、树的基本术语

结点的度:某结点所拥有的子树的个数。

数的度:树中各结点度的最大值。

叶子结点:度为0的结点。

分支结点:度不为0的结点。

孩子结点:某结点的子树;该结点为双亲结点;具有同一个双亲的孩子结点互为兄弟结点。

路径:结点ni是结点ni+1的双亲(1<=i<k),把n1,n2,....,nk称为一条有n1至nk的路径。

路径长度:路径上经过的边数。

祖先:如果从x到y有一条路径,则x是y的祖先;y是x的子孙。

结点的层数:规定根结点的层数为1。

树的深度(高度):树中所有结点的最大层数。

层序标号:将树中结点按照从上层到下层,同层从左到右的次序依次给他们编以从1开始的连续自然数。

有序树:如果一棵树中结点的各子树从左到右是有次序的,即若交换了结点各个子树的相对位置,则构成不同的树;反之为无序树。

森林:m>=0棵互不相交的树的集合。任一棵树删去根结点就变成森林。

5.1.2 树的抽象数据类型定义

ADT Tree

Data

Operating

InitTree 功能:初始化一棵树 后置条件:构造一棵树

DestroyTree 销毁一棵树 释放该树占用的存储空间

PreOrder 前序遍历树 树保持不变

PostOrder 后序遍历树 树保持不变

LeverOrder 层序遍历树 树保持不变

endADT

5.1.3 树的遍历操作

定义:从根结点出发,按照某种次序访问树中所有结点,使得每个结点被访问一次且仅被访问一次。



1、前序遍历 A B D E I F C G H

树空,返回;否则

1)访问根结点

2)按照从左到右的顺序前序遍历根结点的每一棵树

2、后序遍历 D I E F B G H C A

树空,返回:否则

1)按照从左到右的顺序后序遍历根结点的每一棵树

2)访问根结点

3、层序遍历 A B C D E F G H I

从树的第一层开始,自上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。

5.2 树的存储结构

5.2.1 双亲表示法

5.2.2 孩子表示法

5.2.3 双亲孩子表示法

5.2.4 孩子兄弟表示法

5.3 二叉树的逻辑结构

5.3.1 二叉树的定义

n(n>=0)个结点的有限集合,该集合或者为空集(称空二叉树),由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树。

特点:1)每个结点最多有两棵子树; 2)二叉树是有序的,其次序是不能任意颠倒的,即使树中的某个结点只有一棵子树,也要区分它是左是右。

二叉树和树是两种结构。

二叉树有五种基本形态:



1)空二叉树

2)只有一个根结点

3)根结点只有左子树

4)根结点只有右子树

5)根结点既有左子树又有右子树

1、斜树

所有结点都只有左子树的二叉树称为左斜树;所有结点都只有右子树的二叉树称为右斜树。

斜树的结点个数与深度相同。

2、满二叉树

定义:在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上。

特点:1)叶子只能出现在最下一层; 2)只有度为0和度为2的结点。

3、 完全二叉树

定义:对一棵具有n个结点的二叉树按层序编号,若编号为i(1<=i<=n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中的位置完全相同。

特点:1)叶子结点只能出现在最下两层,且最下层的叶子结点都集中在二叉树左侧连续的位置; 2)如果有度为1的结点,只可能有一个,且该结点只有左孩子。

5.3.2 二叉树的基本性质

1、二叉树的第i层上最多有2^(i-1)个结点(i>=1)

2、在一棵深度为k的二叉树中,最多有2^k-1个结点,最少有k个结点。

3、在一棵二叉树中,如果叶子结点的个数为n0,度为2的结点个数为n2,则n0=n2+1.

4、具有n个结点的完全二叉树的深度为|log2(n)|+1

5、对一棵具有n个结点的完全二叉树中的结点从1开始按层序编号,则对于任意的编号为i(1<=i<=n)的结点,有

1)i>1,结点i的双亲编号为|i/2|;否则结点i是根结点,无双亲。

2)2i<=n,结点i的左孩子编号为2i;否则结点i无左孩子。

3)2i+1<=n,结点i的右孩子编号为2i+1;否则无右孩子。

5.3.3 二叉树的抽象数据类型定义

ADT BiTree

Data

Operation

InitBiTree 功能:初始化一棵二叉树 后置条件:构造一棵空的二叉树

DestroyBiTree 销毁一棵二叉树 释放二叉树占有的存储空间

PreOrder 前序遍历二叉树 二叉 树保持不变

InOrder 中序遍历二叉树 二叉 树保持不变

PostOrder 后序遍历二叉树 二叉树保持不变

LeverOrder 层序遍历二叉树 二叉树保持不变

endADT

5.3.4 二叉树的遍历操作



1、前序遍历 A B D G C E F

二叉树空,返回;否则

1)访问根结点

2)前序遍历根结点的左子树

3)前序遍历根结点的右子树

2、中序遍历 D G B A E C F

二叉树空,返回;否则

1)中序遍历根结点的左子树

2)访问根结点

3)中序遍历根结点的右子树

3、后序遍历 G D B E F C A

二叉树空,返回:否则

1)后序遍历根结点的左子树

2)后序遍历根结点的右子树

3)访问根结点

4、层序遍历 A B C D E F G

从二叉树的第一层开始,自上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。



二叉树的后序序列和中序序列可唯一确定一棵二叉树;

前序和后序序列则不能。

5.4 二叉树的存储结构及实现

二叉树的顺序存储结构:用一堆数组存储二叉树中的结点,并用结点的存储位置(下标)表示结点之间的逻辑关系——父子关系。将一般二叉树变成完全二叉树的具体步骤:

(1) 将二叉树按完全二叉树编号。根结点的编号为1,若某结点i有左孩子,则其左孩子的编号为2i;若某结点i有右孩子,则其右孩子编号为2i+1;

(2) 将二叉树中的结点以编号顺序存储到一组数组中。

这种存储方法造成了存储空间的浪费,一般仅仅适合于存储完全二叉树。

5.4.2二叉链表

基本思想:

令二叉树的每个结点对应一个链表结点,链表结点除了存放与二叉树节点有关的数据信息外,还要设置指示左右孩子的指针。

Data为数据域

Lchild为左指针域;

Rchild为右指针域;

采用C++模板机制

Template<class T>

Struct BiNode

{

T data;

BiNode<T> *lchild,*rchild;

};

Template<class T>

class BiTree

{

public:

BiTree (){root=Creat(root);}

~ BiTree (){Release(root);}

void PreOrder(){PreOrder(root);}

void InOrder(){InOrder(root);}

void PostOrder(){PostOrder(root);}

void LeverOrder();

private:

BiNode *root;

BiNode *Creat(BiNode *bt);

void Release(BiNode *bt);

void PreOrder(BiNode *bt);

void InOrder(BiNode *bt);

void PostOrder(BiNode *bt);

};

1、 前序遍历递归算法

Template<class T>

void BiTree::PreOrder(BiNode *bt)

{

if(bt==NULL)return;

else

{

cout<<bt->data;

PreOrder(bt->lchild);

PreOrder(bt->rchild);

}

}

2、 中序遍历递归算法

Template <class T>

void BiTree::InOrder(BiNode *bt)

{

if(bt==NULL)return;

else

{

InOrder(bt->lchild);

cout<<bt->data;

InOrder(bt->rchild);

}

}

3、 后续遍历递归算法

Template<class T>

void BiTree::PostOrder(BiNode *bt)

{

if(bt==NULL)return;

else

{

PostOrder(bt->lchild);

PostOrder(bt->rchild);

cout<<bt->data;

}

}

4、 层序遍历算法

Template <class T>

Void BiTree<T>::LeverOrder()

{

Front=rear=-1;

If(root==NULL) return;

Q[++front]=root;

While(first!=rear)

{

q=Q[++front];

cout<<q->data;

if(q->lchild==NULL) Q[++rear]=q->lchild;

if(q->rchild==NULL) Q[++rear]=q->rchild;

}

}

5、 构造函数

建立二叉链表算法Great

Template <classT>

BiNode <T> *BiTree<T>::Creat(BiNode *bt)

{

cin>>ch;

if(ch=='#')return NULL;

else

{

bt=new BiNode;

bt->data=ch;

bt->lchild=Creat(bt->lchild);

bt->rchild=Creat(bt->rchild);

}

return bt;

}

6、 析构函数

释放二叉链表算法Rlease

Template <T>

void BiTree<T>::Release(BiNode *bt)

{

if(bt!=NULL)

{

Release(bt->lchild);

Release(bt->rchild);

delete bt;

}

}

5.4.3 三叉链表

其结点结构:

5.4.4 线索链表

定义:一个具有n个结点的二叉链表,在2n个指针域中只有n-1个指针域用来存储孩子结点的地址,存在n+1个空指针域,可以利用这些空指针域存放指向该结点在某种遍历序列中的前驱和后继结点的指针。这些指向前驱和后继的结点的指针为线索。加上线索的二叉树称为线索二叉树。相应的,加上线索的二叉链表称为线索链表。

结点结构:

0 lchild 指向改结点的左孩子 0 rchild 指向该结点的右孩子

ltag= rtag=

1 lchild 指向该结点的前驱 1 rchild 指向该结点的后继

在c++中的结构体类型:

Ennm flag{Child,Thread};

Template<class T>

Struct ThrNode

{

T data;

ThrNode<T> *lchild,*rchild;

Flag ltag, rtag;

}

中序线索链表的存储示意图



由于遍历表的遍历次序有四种:前序、中序、后序、层序线索链表,以中序线索链表为例:

Template<class T>

Class InThrBiTree

{

Public:

InThrBiTree();

~ InThrBiTree();

ThrNode *Next(ThrNode<T> *p);

Void InOrder();

Private:

ThrNode<T>*root;

ThrNode<T>*Creat(ThrNode<T> *bt);

Void ThrBiTree(ThrNode<T>*bt,ThrNode<T>*pre);

}:

1、 构造函数:

建立二叉链表(带线索标志)算法Creat

ThrNode <T>*InThrBiTree<T>::Creat(ThrNode<T> *bt)

{

cin>>ch;

if(ch=='#')bt=NULL;

else

{

bt=new ThrNode;

bt->data=ch;

bt->ltag=0;bt->rtag=0;

bt->lchild=Creat(bt->lchild);

bt->rchild=Creat(bt->rchild);

}

return bt;

}

中序线索化链表算法ThrBiTree

Template <class T>

Void InThrBiTree<T>::ThrBiTree(ThrNode <T>*bt, ThrNode<T>*pre)

{

If(bt==NULL)return;

ThrBiTree (bt->lchild,pre);

If(bt->lchild==NULL)

{

Bt->ltag=1;

Bt->lchild=pre;

}

If(bt->rchild=NULL) bt->rtag=1;

If(pre->rtag==1)pre->rchild=bt;

Pre=bt;

ThBiTree(bt->rchild,pre);

}

中序线索链表构造函数算法InThrBiTree

Template<class T>

InThrBiTree<T>::InThrBiTree()

{

Root=Creat(root);

Pre=NULL;

ThrBiTree(root,pre);

}

2、 查找后继结点

中序线索链表查找后继算法Next

Template<class T>

ThrNode<T>*InThrBiTree<T>::Next(ThrNode<T>*p)

{

If(p->rtag==1) q=p->rchild;

Else

{

Q=p->rchild;

While(q->ltag==0)

Q=q->lchild;

}

Return q;

}

3、 遍历操作

中序线索链表的遍历算法InOrder

Template<class T>

Void InThrBiTree<T>::InOrder()

{

If(root==NULL) return;

P=root;

While(p->ltag==0)

P=p->lchild;

Cout<<p->data;

While(p->rchild!=NULL)

{

P=Next(p);

Cout<<p->data;

}

}

5.5 二叉树遍历的非递归算法

5.5.1 前序遍历非递归算法

二叉树前序遍历非递归算法PreOrder

Template<class T>

Void BiTree<T>::PreOrder(BiNode<T>*bt)

{

Top=-1;

While(bt!=NULL||top!=-1)

{

While(bt!=NULL)

{

Cout<<bt->data;

S[++top]=bt;

Bt=bt->lchild;

}

If(top!=-1)

{

Bt=s[top--];

Bt=bt->rchild;

}

}

}

5.5.2 中序遍历非递归算法

二叉树中序遍历非递归算法InOrder

Template <class T>

Void BiTree<T>::PreOrder(BiNode<T>*bt)

{

Top=-1;

While(bt!=NULL||top!=-1)

{

While(bt!=NULL)

{

S[++top]=bt;

Bt=bt->lchild;

}

If(top!=-1)

{

Bt=s[top--];

Cout<<root->data;

Bt=bt->rchild;

}

}

}

5.5.3 后序遍历非递归算法

二叉树后序遍历非递归算法PostOrder

Template <class T>

Void BiTree<T>::PreOrder(BiNode<T>*bt)

{

Top=-1;

While(bt!=NULL||top!=-1)

{

While(bt!=NULL)

{

Top++;

S[top].pre=bt;s[top].flag=1;

Bt=bt->lchild;

}

While(top!=-1&&s[top].flag==2)

{

Bt=s[top--].pre;

Cout<<bt->data;

}

If(top!=-1)

{

S[top].flag=2;

Bt=s[top].pre ->rchild;

}

}

}

5.6 树、森林与二叉树的转换

1、树转换为二叉树

(1)第一个孩子->左孩子

(2) 右兄弟->右孩子

2、森林转化为二叉树

(1)第二棵树->右孩子

(2)第一个孩子->左孩子

(3)右兄弟->右孩子

3、二叉树、树或森林的转化

4 森林的遍历操作

有两种遍历方法:前序根遍历和后序根遍历

前序遍历:A B C D E F G H I J

后序遍历:B A D E F C H J I G

5.7 哈夫曼树(最优二叉树)

给定一组具有确定权值的叶子结点,可以构造出不同的二叉树,将其中带权路径长度最小的二叉树称为哈夫曼树。

例如:给定4个叶子结点,其权值分别为{2,3,4,7},其带权路径长度分别是:

编码过程例题

典型例题:

已知某字符串S为“abcdeacedaeadcedabadadaead” ,对该字符串用[0,1]进行前缀编码,问该字符串的编码至少有多少位。

解:a 9

b 2

c 3

d 7

e 5

哈弗曼编码:

WPL=2*3+3*3+9*2+7*2+5*2=57

本章总结:
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: