树与二叉树

树与二叉树

1、树与二叉树的定义

(Tree)是n(n≥0)个结点的有限集,

​ 若 n = 0,称为空树;

​ 若 n > 0,称为非空树,对于非空树T :有且仅有一个称之为根的结点;

​ 除根结点以外的其余结点可分为m(m>0)个互不相交的有限集T1, T2, …, Tm,

其中每一个集合本身又是一棵树,并且称为根的子树(SubTree);

2、树的基本术语

  • 节点(Node):树中的每个元素被称为节点,包含数据和其他节点的引用;

  • 根节点(Root Node):树的最顶端节点,没有父节点;

  • 子节点(Child Node):直接连接到另一个节点下方的节点;

  • 父节点(Parent Node):有子节点的节点;

  • 兄弟节点(Sibling Node):具有相同父节点的节点;

  • 叶子节点(Leaf Node):没有子节点的节点;

  • 内部节点(Internal Node):至少有一个子节点的节点;

  • 边(Edge):连接父节点和子节点的连线;

  • 路径(Path):树中两个节点之间的边的序列;

  • 深度(Depth):节点到根节点的路径长度;

  • 高度(Height):树中根节点到最远叶子节点的路径长度;

  • 度(Degree):节点的子节点数量;

  • 森林(Forest):由不相交的树组成的集合;

  • 树是特殊的森林;

  • 森林不一定是树

在这里插入图片描述

3、树的其他表达方式

在这里插入图片描述

4、二叉树的定义

  • **二叉树(Binary Tree)**是n(n≥0)个结点所构成的集合,它或为空树(n = 0);或为非空树,对于非空树T:

    有且仅有一个称之为根的结点;

    除根结点以外的其余结点分为两个互不相交的子集T1和T2,分别称为T的左子树和右子树,且T1和T2本身又都是二叉树;

  • 二叉树基本特点:

    结点的度小于等于2;

​ 有序树(子树有序,不能颠倒);

  • 有3个结点的二叉树和树分别有几种不同形态?

​ 二叉树有5种形态:在这里插入图片描述

​ 普通树有2种形态:在这里插入图片描述

5、二叉树的性质

**完全二叉树:**深度为 k 的具有n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树中编号从1~n的结点一一对应,只有最后一层叶子不满,且全部集中在左边;

在这里插入图片描述

满二叉树:一棵深度为k 且有2 k -1个结点的二叉树;

特点

  • 每层结点数都是最大结点数(即每层都满);

  • 叶子结点全部在最底层;

  • 每一结点位置都有元素;

满二叉树是叶子一个也不少的树,而完全二叉树虽然前n-1层是满的,但最底层却允许在右边缺少连续若干个结点。满二叉树是完全二叉树的一个特例;

在这里插入图片描述

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

2、深度为k的二叉树至多有2 k-1个结点(k>=1);

3、对于任何一棵二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则n0=n2+1;

​ 节点:n1+n2+n0=n;

​ 边:n2下接两条边,n1下接一条边,可得2*n2+n1=n-1;

​ 相减n0-n2=1;

4、具有n个节点的完全二叉树深度为⌈log2(n+1)⌉或⌊log2n⌋+1;

​ 满二叉树的度为k=log2(n+1),比如结点数为15的满二叉树,度为4.完全二叉树的结点数一定少于等于同样度数的满二叉树的结点数2 k-1,但是一定多于2 k-1-1。即满足2 k-1-1<n<=2k-1, 2 k-1<=n<2k , k-1=<log2n<k,k= ⌊log2n⌋+1 ;

5、对有n个结点的完全二叉树,若从上至下、从左至右编号,则编号为i 的结点,有:

  • 若2i>n,则结点 i 为叶子结点,无左孩子;否则,左孩子的结点为2i;
  • 若2i+1>n,则结点 i 无右孩子;否则,其右孩子编号必为2i+1;
  • 若 i=1,则结点i是二叉树的根,无双亲;若 i>1,则其双亲是 i/2 ;

二叉树的存储与遍历

1、二叉树的存储

  • 二叉树的顺序存储

按满二叉树的结点层次编号,依次存放二叉树中的数据元素;

在这里插入图片描述

特点

结点间关系蕴含在其存储位置中;

浪费空间,适于存满二叉树和完全二叉树;

  • 二叉树的链式存储

1、二叉链表

在这里插入图片描述

typedef struct BiNode{

    TElemType data;

    struct BiNode *lchild,*rchild;		//左右孩子指针

}BiNode,*BiTree;

2、三叉链表

在这里插入图片描述

typedef struct TriTNode{
    
    TelemType data;

    struct TriTNode *lchild,*parent,*rchild;

}TriTNode,*TriTree;

2、二叉树的遍历

  • 遍历指按某条搜索路线遍访每个结点且不重复(又称周游);
  • 遍历是树结构插入、删除、修改、查找和排序运算的前提,是二叉树一切运算的基础和核心。

​ DLR — 先(根)序遍历,即先根再左再右;

​ LDR — 中(根)序遍历,即先左再根再右;

​ LRD — 后(根)序遍历,即先左再右再根;

在这里插入图片描述

​ 先序遍历:A B D E C

​ 中序遍历:D B E A C

​ 后序遍历:D E B C A

3、遍历算法的实现

  • 先序遍历:A B C D

若二叉树为空,则空操作,否则

访问根结点(D)

先序遍历左子树(L)

先序遍历右子树(R)

在这里插入图片描述

  • 中序遍历:B D A C

若二叉树为空,则空操作,否则

中序遍历左子树 (L)

访问根结点 (D)

中序遍历右子树 ®

在这里插入图片描述

  • 后序遍历:D B C A

若二叉树为空,则空操作,否则

后序遍历左子树 (L)

后序遍历右子树 ®

访问根结点 (D)

在这里插入图片描述

Status PreOrderTraverse(BiTreeT){

    if(T==NULL) return OK; else{    

        visit(T);

        PreOrderTraverse(T->lchild); 

        PreOrderTraverse(T->rchild);
    }

}
  • 何为递归?程序反复调用自身即是递归。

既然递归是一个反复调用自身的过程,这就说明它每一级的功能都是一样的,因此我们只需要 关注一级递归的解决过程即可;

Binary Tree递归三要素:

1)找整个递归的终止条件:递归应该在什么时候结束?

2)找返回值:应该给上一级返回什么信息?

3)本级递归应该做什么:在这一级递归中,应该完成什么任务?

  • 写出求二叉树的最大深度的递归遍历算法

如果二叉树为空,二叉树的深度为0;

如果二叉树不为空,二叉树的深度 = max(左子树深度,右子树深度) + 1;

int deepth(TreeNode* root){    

    if(root == NULL) 

        return 0;    

    int left = deepth(root->left);   

    int right = deepth(root->right);    

    return 1+max(left,right);

}
  • 写出求二叉树叶子结点个数的递归遍历算法

如果二叉树为空,叶子结点个数为0;

如果二叉树只有根节点,叶子结点个数为1;

如果二叉树不为空,叶子结点个数=左子树的叶子结点个数+右子树的叶子结点个数;

int LeadCount(BiTree T){

    if(T==NULL) //如果是空树返回0

        return 0;

    if (T->lchild == NULL && T->rchild == NULL)

        return 1; //如果是叶子结点返回1

    else return LeafCount(T->lchild) + LeafCount(T->rchild);

}
  • 已知一棵二叉树的中序序列和后序序列分别是BDCEAFHG 和 DECBHGFA,请画出这棵二叉树

由后序遍历特征,根结点必在后序序列尾部(A);

由中序遍历特征,根结点必在其中间,而且其左部必全部是左子树子孙(BDCE),其右部必全部是右子 树子孙(FHG);

继而,根据后序中的DECB子树可确定B为A的左孩子,根据HGF子串可确定F为A的右孩子;以此类推。

在这里插入图片描述

线索二叉树

1、线索二叉树的定义

为什么要研究线索二叉树?

普通二叉树只能找到结点的左右孩子信息,而该结点的 直接前驱和直接后继只能在遍历过程中获得;

若将遍历后对应的有关前驱和后继预存起来,则从第一 个结点开始就能很快“顺藤摸瓜”而遍历整个树;

在这里插入图片描述

利用二叉链表中的空指针域保存信息

若结点有左子树,则lchild指向其左孩子;否则,lchild指向其直接前驱(即线索); 若结点有右子树,则rchild指向其右孩子;否则,rchild指向其直接后继(即线索);

  • 为区分lchild、rchild指针,设置两个标志域

lchild LTag data RTag rchild

LTag=0, lchild域指向左孩子;

LTag=1, lchild域指向其前驱;

RTag=0, rchild域指向右孩子;

RTag=1, rchild域指向其后继;

2、二叉树的线索化

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

树、二叉树、森林的转换

1、树的存储

  • 双亲表示法

在这里插入图片描述

  • 孩子表示法

在这里插入图片描述

  • 孩子兄弟表示法(二叉链表表示法)

在这里插入图片描述

2、树与二叉树的转换

在这里插入图片描述

树转二叉树

加线:在所有兄弟结点之间加一条连线;

去线:对树中每个结点,只保留它与第一个孩子结点的连线,删除与其他孩子结点之间的连线;

层次调整:以树的根结点为轴心,将整棵树顺时针旋转定的角度,使之结构层次分明;

在这里插入图片描述

  • 二叉树转树

加线:若某结点的左孩子结点存在,则将这个左孩子的右孩子结点及分支的所有右孩 子,将该结点与这些右孩子结点用线连接起来;

去线:删除原二叉树中所有结点与其右孩子结点的连线;

层次调整:使之结构层次分明;

在这里插入图片描述

3、森林与二叉树的转换

在这里插入图片描述

  • 森林转二叉树

森林是由若干棵树组成的,所以完全可以理解为,森林中的每一棵树都是兄弟, 可以按 照兄弟的处理办法来操作;

把每个树转换为二叉树;

第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵叉树的根结点作为前一棵 二叉树的根结点的右孩子,用线连接起来。当所有的二叉树连接起来后就得到了由 森林转换来的二叉树;

在这里插入图片描述

  • 二叉树转森林

一棵二叉树的根结点有没有右孩子,有就是森林,没有就是一棵树。转换成森林,步骤 如下:

从根结点开始 若右孩子存在,则把与右孩子结点的连线 ,再查看分离后的二叉树, 若右孩子存在,则连续去线,直到所有右孩子连线都删除为止,得到分离的二叉树;

再将每棵分离后的二叉树转换为树即可;

哈夫曼树

1、哈夫曼树的定义

假设有n个权值w1 ,w2 , …… , wn ,构造有n个叶子的二叉树,每个叶子的权值是n个权值之一。 这样的二叉树可以构造多个,其中必有一个(或几个)是带权路径长度WPL最小的。 达到WPL最小的二叉树就称为最优二叉树或哈夫曼树。

权值分别为7,5,2,4,构造有4个叶子结点的二叉树;

在这里插入图片描述

  • 哈夫曼树的特点

没有度为1的结点;

n个叶子结点的哈夫曼树共有2n-1个结点;

哈夫曼树的任意非叶节点的左右子树交换后仍是哈夫曼树;

在不考虑结点权值的情况下,如果二叉树A通过任意结点的左右子树交换,可以变成二叉树B,那么 就称A和B是同构的。也就是说,与一棵哈夫曼树同构的二叉树都是哈夫曼树;对同一组权值 {w1 ,w2 , …… , wn},是否存在不同构的两棵哈夫曼树呢?

对一组权值{ 1, 2 , 3, 3 },不同构的两棵哈夫曼树:

在这里插入图片描述

2、构造哈夫曼树

1、根据给定的n个权值{w1,w2,……wn},构造n棵只有根结点的二叉树;

2、在森林中选取两棵根结点权值最小的树作左右子树,构造一棵新的二叉树,置 新二叉树根结点权值为其左右子树根结点权值之和;

3、在森林中删除这两棵树,同时将新得到的二叉树加入森林中;

4、重复上述两步,直到只含一棵树为止,这棵树即哈夫曼树;

  • 基本思想:使权大的结点靠近根;
  • 操作要点:对权值的合并、删除与替换,总是合并当前值最小的两个;

给定权值1 5 2 6 10 7,要求构造出哈夫曼树

在这里插入图片描述

3、哈夫曼编码

在远程通讯中,要将待传字符转换成二进制的字符串,怎样编码才能使它们组成 的报文在网络中传得最快?出现次数较多的字符采用尽可能短的编码

在这里插入图片描述

在这里插入图片描述

关键:要设计长度不等的编码,则必须使任一字符的编码都不是另一个字符的编码 的前缀-前缀编码

编码方式:

1、统计每个字符在电文中出现的平均概率;

2、利用哈夫曼树的特点:权越大的叶子离根越近;将每个字符的概率值作为权值,构造哈夫曼树。概 率越大的结点,路径越短;

3、在哈夫曼的每个分支上标记:

​ (1)结点的左分支标0,右分支标1;

​ (2)从根到每个叶子的路径上的标号连接起来,作为该叶子代表的字符的编码;

某系统在通讯时,只出现C,A,S,T,B五种字符,其出现频率 w={2,4,2,3,3},试 设计Huffman编码。

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/405d443126eb43b1b88c517aefa8ff13.png#pic_center在这里插入图片描述

4、哈夫曼树构造算法的实现

typedef  struct{
    
    int weght;

    int parent,lch,rch;
 
}*HuffmanTree;

1、初始化HT[1…2n-1]:lch=rch=parent=0;

2、进行以下n-1次合并,依次产生HT[i],i=n+1…2n-1;

3、在HT[1…i-1]中选两个未被选过的weight最小的两个结 点HT[s1]和HT[s2] (从parent = 0 的结点中选);

4、修改HT[s1]和HT[s2]的parent值: parent=i;

5、置HT[i].weight=HT[s1].weight + HT[s2].weight ,lch=s1, rch=s2;

设n=4, w={70,50,20,40} 试设计 huffmancode (m=2*4-1=7)

在这里插入图片描述

在这里插入图片描述

void CreatHuffmanTree (HuffmanTree HT,int n){

    if(n<=1)return;

    m=2*n-1;

    HT=new HTNode[m+1];		//0号单元未用,HT[m]表示根结点

    for(i=1;i<=m;++i)  {

        HT[i].lch=0;HT[i].rch=0;HT[i].parent=0;

    }

    for(i=1;i<=n;++i)

        cin>>HT[i].weight; 

    for( i=n+1;i<=m;++i) { 

        Select(HT,i-1, s1, s2);		//选择两个其双亲域为0,且权值最小的结点,并返回它们在HT中的序号s1和s2

        HT[s1].parent=i;   HT[s2] .parent=i;		//表示从F中删除s1,s2

        HT[i].lch=s1;   

        HT[i].rch=s2;		//s1,s2分别作为i的左右孩子

        HT[i].weight=HT[s1].weight + HT[s2] .weight;		//i 的权值为左右孩子权值之和

    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值