数据结构 第六章 树和二叉树

目录

6.1 树的定义

树的定义

逻辑特点

基本术语

6.2 二叉树的定义

二叉树基本特点

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

二叉树的顺序存储

二叉树的链式存储

6.3 遍历二叉树和线索二叉树

先序遍历

中序遍历

后序遍历

层序遍历

求高度

求结点总数

求叶子结点总数

先序输出叶子结点

计算给定二叉树T的宽度。二叉树的宽度是指各层结点数的最大值。

线索化二叉树

线索化二叉树的几个术语

6.4 树和森林

树的存储结构--二叉链表表示法

树、森林与二叉树之间的相互转换

1.树或森林转换为二叉树

2.二叉树转化为树或森林

6.5 哈夫曼树及其应用

构造过程


6.1 树的定义

树的定义:

树(Tree)是nn0)个结点的有限集,它或为空树(= 0);或为非空树,对于非空树T

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

2)除根结点以外的其余结点可分为mm0)个互不相交的有限集T1, T2, …, Tm, 其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)。

逻辑特点:

1.有且仅有一个结点没有前驱结点,该结点为树的根节点;
2.除了根节点外,每个节点有且仅有一个直接前驱结点;
3.包括根节点在内,每个结点可以有多个后继结点

基本术语

根——即根结点(没有前驱)

叶子——即终端结点(没有后继)

森林——m棵不相交的树的集合(例如删除A后的子树个数)

有序树——结点各子树从左至右有序,不能互换(左为第一)

无序树——结点各子树可互换位置。

双亲——即上层的那个结点(直接前驱)

孩子——即下层结点的子树的根(直接后继)

兄弟——同一双亲下的同层结点(孩子之间互称兄弟)

堂兄弟——即双亲位于同一层的结点(但并非同一双亲)

祖先——即从根到该结点所经分支的所有结点

子孙——即该结点下层子树中的任一结点

结点——即树的数据元素

结点的度——结点挂接的子树数

结点的层次——从根到该结点的层数(根结点算第一层)

终端结点——即度为0的结点,即叶子

分支结点——即度不为0的结点(也称为内部结点)

树的度——所有结点度中的最大值

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

6.2 二叉树的定义

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

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

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

二叉树基本特点:

结点的度小于等于2

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

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

ADT BinaryTree{
数据对象D:D是具有相同特性的数据元素的集合。
数据关系R:
若D=Φ,则R= Φ ;
若D≠Φ,则R= {H};存在二元关系:
 ①  root 唯一         //关于根的说明
 ② Dj∩Dk= Φ     //关于子树不相交的说明
 ③  ……               //关于数据元素的说明
 ④  ……              //关于左子树和右子树的说明

基本操作 P:
CreateBiTree(&T,definition)
      初始条件;definition给出二叉树T的定义。
      操作结果:按definition构造二叉树T。

PreOrderTraverse(T)
      初始条件:二叉树T存在。
      操作结果:先序遍历T,对每个结点访问一次。
InOrderTraverse(T)
      初始条件:二叉树T存在。
      操作结果:中序遍历T,对每个结点访问一次。
PostOrderTraverse(T)
      初始条件:二叉树T存在。
      操作结果:后序遍历T,对每个结点访问一次。

}ADT BinaryTree

二叉树的性质和存储结构

性质1: 在二叉树的第i层上至多有2^(i-1)个结点

性质2: 深度为k的二叉树至多有2^k-1个结点

性质3: 对于任何一棵二叉树,若2度的结点数有n2个,则叶子数n0必定为n21 (即n0=n2+1

满二叉树:一棵深度为k 且有2^k -1个结点的二叉树。(特点:每层都“充满”了结点)

完全二叉树:深度为k n个结点的二叉树,当且仅当其每一个结点都与深度为k 的满二叉树中编号从1n的结点一一对应

性质4: 具有n个结点的完全二叉树的深度必为[log2n]1

性质5: 对完全二叉树,若从上至下、从左至右编号,则编号为i 的结点,其左孩子编号必为2i,其右孩子编号必为2i1;其双亲的编号必为i/2

二叉树的顺序存储

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

特点:

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

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

二叉树的链式存储

typedef struct BiNode{
   TElemType   data;
   struct  BiNode   *lchild,*rchild; //左右孩子指针
}BiNode,*BiTree; 

三叉链表

typedef struct TriTNode
{  
    TelemType data;
    struct TriTNode *lchild,*parent,*rchild;
 }TriTNode,*TriTree;

6.3 遍历二叉树和线索二叉树

遍历定义——指按某条搜索路线遍访每个结点且不重复(又称周游)。

遍历用途——它是树结构插入、删除、修改、查找和排序运算的前提,是二叉树一切运算的基础和核心。 

口诀:

DLR—先序遍历,即先根再左再右

LDR—中序遍历,即先左再根再右

LRD—后序遍历,即先左再右再根

先序遍历

Status PreOrderTraverse(BiTree T){
  if(T==NULL) return OK; //空二叉树
  else{    
     cout<<T->data; //访问根结点
     PreOrderTraverse(T->lchild); //递归遍历左子树
     PreOrderTraverse(T->rchild); //递归遍历右子树
    }
}
 

中序遍历

Status InOrderTraverse(BiTree T){
  if(T==NULL) return OK; //空二叉树
  else{    
     InOrderTraverse(T->lchild); //递归遍历左子树
  cout<<T->data; //访问根结点
     InOrderTraverse(T->rchild); //递归遍历右子树
    }
}
 

后序遍历

Status PostOrderTraverse(BiTree T){
  if(T==NULL) return OK; //空二叉树
  else{    
     PostOrderTraverse(T->lchild); //递归遍历左子树
     PostOrderTraverse(T->rchild); //递归遍历右子树
     cout<<T->data; //访问根结点
    }
}
 

如果去掉输出语句,从递归的角度看,三种算法是完全相同的,或说这三种算法的访问路径是相同的,只是访问结点的时机不同

时间效率:O(n) //每个结点只访问一次

空间效率:O(n) //栈占用的最大辅助空间

层序遍历

void LevelorderTraversal( BinTree BT )
{
    if(!BT)
        return;
    BinTree q[505];
    int front = 0,rear = 0;
    q[rear++] = BT;
    while(front < rear)
    {
        printf(" %c",q[front]->Data);
        if(q[front]->Left)
        {
            q[rear++] = q[front]->Left;
        }
        if(q[front]->Right)
        {
            q[rear++] = q[front]->Right;
        }
        front++;
    }
}

求高度

int GetHeight( BinTree BT )
{
    if(!BT)
        return 0;
    int lh = GetHeight(BT->Left);
    int rh = GetHeight(BT->Right);
    if(lh>rh)
        return lh+1;
    return rh+1;
}

求结点总数

int NodeCount(BiTree T)
{
  if(T == NULL ) return 0;
  return NodeCount(T->lchild)+NodeCount(T->rchild)+1;
}

求叶子结点总数

int LeadCount(BiTree T){
 	if(T==NULL) 	//如果是空树返回0
		return 0;
	if (T->lchild == NULL && T->rchild == NULL)
		return 1; //如果是叶子结点返回1
	return LeafCount(T->lchild) + LeafCount(T->rchild);
}

先序输出叶子结点

void PreorderPrintLeaves( BinTree BT )
{
    if(!BT)
    return;
    if(!BT->Left && !BT->Right)printf(" %c",BT->Data);
    PreorderPrintLeaves(BT->Left);
    PreorderPrintLeaves(BT->Right);
}

计算给定二叉树T的宽度。二叉树的宽度是指各层结点数的最大值。

typedef struct TreeNode *BinTree;
struct TreeNode
{
   int Key;
   BinTree  Left;
   BinTree  Right;
};
int Width( BinTree T )
{
   BinTree  p;
   Queue Q;
   int Last, temp_width, max_width;
   temp_width = max_width = 0;
   Q = CreateQueue(MaxElements);
   Last = Queue_rear(Q);
   if (T == NULL) return 0;
   else 
    {
        Enqueue(T, Q);
        while (!IsEmpty(Q)) 
        {
            p = Front_Dequeue(Q); 
            temp_width++;
            if ( p->Left != NULL )  Enqueue(p->Left, Q);

            if ( p->Right != NULL )  Enqueue(p->Right, Q);
            
            if ( Queue_front(Q) > Last ) 
            {
                Last = Queue_rear(Q);
                if ( temp_width > max_width ) max_width = temp_width;

                temp_width = 0;
            } 
        }
              return  max_width;
   } 
} 

若二叉树中各结点的值均不相同,则:

由二叉树的前序序列和中序序列,或由其后序序列和中序序列均能唯一地确定一棵二叉树,

但由前序序列和后序序列却不一定能唯一地确定一棵二叉树。

线索化二叉树

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

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

例如中序遍历结果:B D C E A F H G,实际上已将二叉树转为线性排列,显然具有唯一前驱和唯一后继!

1)若结点有左子树,则lchild指向其左孩子;

       否则, lchild指向其直接前驱(即线索)

2)若结点有右子树,则rchild指向其右孩子;

       否则, rchild指向其直接后继(即线索)

为了避免混淆,增加两个标志域

lchild

LTag

data

RTag

rchild

LTag  :LTag=0, lchild域指向左孩子;
       LTag=1, lchild域指向其前驱。

RTag  :RTag=0, rchild域指向右孩子;
       RTag=1, rchild域指向其后继。

线索化二叉树的几个术语

线索:指向结点前驱和后继的指针

线索链表:加上线索二叉链表

线索二叉树:加上线索的二叉树(图形式样)

线索化:对二叉树以某种次序遍历使其变为线索二叉树的过程

6.4 树和森林

树的存储结构--二叉链表表示法

typedef struct CSNode{
  ElemType          data;
  struct CSNode     *firstchild,*nextsibling;
}CSNode,*CSTree;

树、森林与二叉树之间的相互转换

树或森林与二叉树之间存在一一对应的关系,相互之间可以进行转换

1.树或森林转换为二叉树

1)凡是兄弟就用线连起来

2除第一个子女外的其他子女均去掉到父母的连线

2.二叉树转化为树或森林

B是一棵二叉树,rootB的根,Lroot的左子树,Rroot的右子树。则对应于B的森林F(B)的定义为:

⑴ 若B为空,则对应的森林F(B)也为空;

⑵ 若B非空,则F(B)是第一棵树T1,加上森林F(R),其中树T1的根为rootroot的子树为F(L)

6.5 哈夫曼树及其应用

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

采用二叉树设计前缀编码  左分支用“0”,右分支用“1”

分解接收字符串:遇“0”向左,遇“1”向右;一旦到达叶子结点,则译出一个字符,反复由根出发,直到译码完成。

特点:每一码都不是另一码的前缀,绝不会错译! 称为前缀码                                                 

基本思想:使权大的结点靠近根

操作要点:对权值的合并、删除与替换,总是合并当前值最小的两个

构造过程

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

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

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

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

一棵有n个叶子结点的Huffman树有      2n - 1    个结点

 

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值