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

目录

1.树和二叉树的定义

1.1 树的定义

1.2 二叉树的定义

1.3 基本术语

1.4 抽象数据类型定义

2 二叉树

2.1 二叉树的性质

2.2 二叉树的存储结构

1)顺序存储结构

2)链式存储结构(常用)

2.3 遍历二叉树

1)先序遍历

2)中序遍历

3)后序遍历

4)中序遍历非递归算法

5) 层次遍历

2.4 二叉树遍历的应用

1)建立二叉链表

2)复制二叉树

3)计算二叉树深度

4)计算二叉树结点总数

2.5 线索二叉树

3 树和森林

4 哈夫曼树

5 小结



树结构和线性结构的比较
线性结构树结构
第一个数据元素 无前驱根节点 无双亲
最后一个数据元素 无后继叶子结点 无孩子
其他数据元素:一个前驱,一个后继其他结点(中间结点):一个双亲,多个孩子
一对一一对多

2 二叉树

1 满二叉树:深度为k且有 个结点的二叉树。

  1. 每一层上的结点数都是最大结点(每层都满)。
  2. 叶子结点全部都在最底层。

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

  1. 叶子只可能分布在层次最大的两层上。
  2. 对任一结点,若其右子树的最大层次为i,则其左子树的最大层次必为i或i+1。

【注】在满二叉树中,从最后一个结点开始,连续去掉任意个结点,即是一颗完全二叉树。

2.1 二叉树的性质

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

性质2 深度为k的二叉树至多有2^{k}-1个结点(k≥1)。

性质3 对任何一颗二叉树T,如果其叶子数为,度为2的结点数为,则。

性质4 具有n个结点的完全二叉树的深度为。

(:称作x的底,表示不大于x的最大整数,反之,表示不小于x的最小整数)

(表明完全二叉树结点数n与深度k之间的关系)

性质5 如果有一棵有n个结点的完全二叉树的结点按层序编号,则对任一结点i(1≤≤n):

  1. 双亲(parent)是结点 i/2
  2. 左孩子(lchild)是结点 2i
  3. 右孩子(rchild)是结点 2i+1

(表明完全二叉树双亲结点编号孩子结点编号之间的关系)

2.2 二叉树的存储结构

1)顺序存储结构

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

 
  • 顺序存储缺点:深度为k的且只有k个结点的单支树需要长度为2^{k}-1的一维数组。 浪费空间,适合存放满二叉树和完全二叉树。 

2)链式存储结构(常用)

      2.1 二叉链表

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

      2.2 三叉链表* 

typedef struct TriNode{
   TElemType data;                   //结点数据域
   struct TriNode *lchild,*parent,*rchild;    //双亲左右孩子指针
}TriNode,*TriTree;

2.3 遍历二叉树

规定先左后右,则有三种情况:

(若二叉树为空,则空操作),否则:

  1. 根左右,DLR——先序遍历(PrdOrderTraverse);
  2. 左根右,LDR——中序遍历(InOrderTraverse);
  3. 左右根,LRD——后序遍历(PostOrderTraverse);
  •  由二叉树的先序序列和中序序列,或由二叉树的后序序列和中序序列可以确定唯一一颗二叉树。

 (由先序序列确定根,由中序序列确定左右子树)

1)先序遍历

Status PrdOrderTraverse (BiTree T){
     if(T==NULL) return OK;             //若二叉树非空
     else
     {
         cout<<T->data;                 //访问根节点
         PrdOrderTraverse(T->lchild);   //先序遍历左子树
         PrdOrderTraverse(T->rchild);   //先序遍历右子树
     }
}

void Pre(BiTree *T){

    if(T!=NULL)

    {

     printf ("%d\t",T->data);

     pre (T->lchild);

     pre (T->rchild);

    }

}

2)中序遍历

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

3)后序遍历

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

 从递归的角度看,去掉输出语句,三种算法完全相同,访问的路径相同,访问结点的时机和顺序不同。每个结点经过3次。

遍历算法的分析:

  • 时间效率:O_{\left ( n \right )}(每个结点只访问一次)
  • 空间效率:O_{\left ( n \right )}(栈占用的最大辅助空间)

4)中序遍历非递归算法

的思想实现中序遍历。

基本思想:

  1. 建立一个栈;
  2. 根结点进栈,遍历左子树
  3. 根结点出栈,输出根节点,遍历右子树
Status InOrderTraverse (BiTree T){
    BiTree p;
    InitStack (S);
    p=T;                             //初始化一个空栈,指针p指向根节点
    q=new BiTNode;                   //申请结点q,用来存放栈顶出的元素
    while(p||!StackEmpty(S))         //所有元素出栈
    {
       if(p)                         //p非空
        {
          Push(S,p);                 //根指针进栈
          p=p->lchild;               //遍历左子树
        else                         //p非空
        {
          Pop(S,q);
          printf( "%c",q->data);
          cout<<q->data;             //访问根节点
          p=q->rchild;               //遍历右子树
        }
    }                                //while
    return OK;
} 
    

5) 层次遍历

(用队列的思想实现遍历)

【基本思想】

  1. 根结点进队;
  2. 队不空时循环。从队列中列出一个结点*p,:若有左孩子结点,将左孩子入队;若有右孩子结点,则将右孩子入队。

【定义】 

typedef struct{
     BTNode data[MaxSize];   //存放队中元素
     int front,rear;         //队头和队尾指针
}SqQueue;                    //顺序循环队列类型

二叉树层次遍历算法: 

void LecelOrder(BTNode *b){
     BTNode *p;    SqQueue *qu;
     InitQueue(qu);   
     

2.4 二叉树遍历的应用

1)建立二叉链表

先序遍历序列建立二叉树的二叉链表。                  

 【例】已知先序序列为:ABCDEF:

  ABC##DE#G##F###

Status CreateBiTree(BiTree &T){
   cin>>ch;
   if(ch=="#") T=NULL;
   else {
         if (!(T=(BiTNode*)malloc(sizeof(BiTNode))))
         T=new BiTNode;       //
         T->data=ch;
         CtrateBiTree(T->lchild);   //
         CtrateBiTree(T->rchild);   //
        }
}

2)复制二叉树

【算法步骤】

如果是空树,递归结束;否则:

  1. 申请一个新结点空间,复制根节点。
  2. 递归复制左子树。
  3. 递归复制右子树。
Status CreateBiTree(BiTree &T){
   

3)计算二叉树深度

【算法步骤】

如果是空树,深度为0,递归结束,否则:

  1. 递归计算左子树的深度m;
  2. 递归计算右子树的深度n;
  3. 如果m大于n,二叉树的深度为m+1,否则为n+1。
Status CreateBiTree(BiTree &T){
   

4)计算二叉树结点总数

【算法步骤】

  1. 如果是空树,则结点个数为0;
  2. 否则,结点个数为左子树的结点个数+右子树的结点个数+1
Status NodeCount(BiTree &T){
   if(T=NULL)
      return 0;                           //空树结点为0
   else
       return NodeCount(T->lchild)+
              NodeCount(T->rchild)+1;     //结点个数=左子树结点个数+右子树结点个数+1
}

2.5 线索二叉树

利用二叉链表中的空指针域:

  1.  如果某结点左孩子为空,则将空的左孩子指针域改为指向其前驱;
  2.  如果某结点右孩子为空,则将空的右孩子指针域改为指向其后继;

对二叉链表中每个结点增设两个标志域ltag rtag

  • ltag=0——lchild 指向该结点的左孩子
  • ltag=1——lchild 指向该结点的前驱
  • rtag=0——rchild 指向该结点的右孩子
  • rtag=1——rchild 指向该结点的后继

结点结构:

lchildltagdatartagtchild
typedef struct BiThrNode
{
   int data;
   int ltag,rtag;                      //定义左右标志
   struct  BiThrNode *lchild,rchild;   //定义左右孩子指针
} BiThrNode, * BiThrTree;

增设一个头结点:

  • ltag=0——lchild指向根节点,
  • rtag=1——rchild指向最后一个结点
  • 遍历序列里第一个结点的lc域和最后一个结点的rc域都指向头结点。

3 树和森林

  • 树:n个结点结点的有限集合。
  • 森林:m(m≥0)棵互不相交的树的集合。

5.6.1  树的存储结构

1)双亲表示法

dataparent

2)孩子表示法

childnext

带双亲的孩子链表

3)孩子兄弟表示法√

(二叉树表示法,二叉链表表示法)

firstchild 左孩子datanextsibling 右兄弟
typedef struct CSNode{
   ElemType   data;
   struct CSNode  *firstchild,*nextsibling;
}CSNode,*CSTree;

 

5.6.3 树和森林的遍历

1)树的遍历

  • 先根遍历
  • 后根遍历
  • 层次遍历:自上而下自左至右访问结点。

2)森林遍历

将森林看成三部分:

  1. 第一棵树的根结点。
  2. 第一棵树的子树森林。
  3. 其他树构成的森林。

 先序遍历:(依次从左到右对森林中的每一棵树进行先根遍历

  1. 访问森林中第一棵树的根结点。
  2. 先序遍历森林中第一棵树的子树森林。
  3. 先序遍历森林中其余树的森林。

中序遍历:(依次从左到右对森林中的每一棵树进行后根遍历

  1. 中序遍历森林中第一棵树的子树森林。
  2. 访问森林中第一棵树的根结点。
  3. 中序遍历森林中其余树的森林。

 

5.6.2 森林与二叉树的转换

给定一棵树,可以找到唯一的与之对应的一颗二叉树。

 1)树→二叉树

兄弟相连留长子

  1. 加线:在兄弟之间加连线
  2. 去线:每个结点除了左孩子外,去除其余孩子之间的连线。
  3. 旋转:以树的根结点为轴心, 整棵树顺时针旋转45°。

2)二叉树→树

左孩右右连双亲,去掉原来右孩线

  1.  加线:若P结点是双亲结点的左孩子,则将p沿分支找到所有的右孩子,都与p的双亲连线。
  2. 去线:去掉原二叉树中双亲与右孩子间的连线。
  3. 调整:将结点按层次排列,形成树结构。

3)森林→树

树变二叉根相连

  1. 将各棵树分别转换成二叉树。
  2. 每棵树的根结点连线。
  3. 以第一棵树根结点为二叉树的根,以根结点为轴心,顺时针旋转45°形成二叉树结构。 

 4)树→森林

去掉全部右孩线,孤立二叉再还原

  1. 去线:将二叉树中根结点与右孩子连线,沿右分支找到的所有右孩子连线全去掉,使变成孤立的二叉树。
  2. 还原:将孤立的二叉树还原成树。 

​​​​​​​

4 哈夫曼树

哈夫曼树(最优树)((WPL)带权路径长度最短的树)

4.1  基本概念

路径:从树中一个结点到另一个结点之间的分支

结点的路径长度:两结点间路径上的分支数

树的路径长度:从树根到每一个结点的路径长度之和。

(结点数目相同的二叉树中,路径长度最短的二叉树是完全二叉树。)

权(weight):将树中结点赋给一个有某种意义的数值,这个数值称为该结点的权。

结点的带权路径长度:从根结点到该结点之间的路径长度与该结点的权的乘积。

(满二叉树不一定是哈夫曼树;哈夫曼树中权越大的叶子离根越近)

4.2 算法

包含n棵树的森林要经过n-1次合并才能形成哈夫曼树,共产生N-1个新结点。 

4.3 哈夫曼编码

5 小结

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
1.两个串相等的充要条件是( )。A.串长相等B.串长任意 C.串中各位置字符任意 D.串中各位置字符均对应相等 2.对称矩阵的压缩存储:以行序为主序存储下三角中的元素,包括对角线上的元素。二维下标为( i, j ),存储空间的一维下标为k,给出k与 i, j (i<j)的关系k=( ) (1<= i, j <= n , 0<= k < n*(n+1)/2)。 A.i*(i-1)/2+j-1 B.i*(i+1)/2+j C.j*(j-1)/2+i-1 D.j*(j+1)/2+i 3.二维数组A[7][8]以列序为主序的存储,计算数组元素A[5][3] 的一维存储空间下标 k=( )。 A.38 B.43 C.26 D.29 4.已知一维数组A采用顺序存储结构,每个元素占用4个存储单元,第9个元素的地址为144,则第一个元素的地址是( )。A.108 B.180 C.176 D.112 5. 下面( )不属于特殊矩阵。 A.对角矩阵 B. 三角矩阵C. 稀疏矩阵 D. 对称矩阵 6. 假设二维数组M[1..3, 1..3]无论采用行优先还是列优先存储,其基地址相同,那么在两种存储方式下有相同地址的元素有( )个。 A. 3 B. 2 C. 1 D. 0 7. 若Tail(L)非空,Tail(Tail(L))为空,则非空广义表L的长是( )。(其中Tail表示取非空广义表的表尾) A. 3 B. 2 C. 1 D. 0 8.串的长是( )。 A.串中不同字母的个数 B.串中不同字符的个数C.串中所含字符的个数,且大于0 D.串中所含字符的个数 9.已知广义表(( ),(a), (b, c, (d), ((d, f)))),则以下说法正确的是( )。A.表长为3,表头为空表,表尾为((a), (b, c, (d), ((d, f))))B.表长为3,表头为空表,表尾为(b, c, (d), ((d, f)))C.表长为4,表头为空表,表尾为((d, f)) D.表长为3,表头为(()),表尾为((a), (b, c, (d), ((d, f))))10.广义表A=(a,b,c,(d,(e,f))),则Head(Tail(Tail(Tail(A))))的值为( )。(Head与Tail分别是取表头和表尾的函数) A.(d,(e,f)) B.d C.f D.(e,f)二、填空题(每空 2 分,共 8 分)。 1.一个广义表为 F = (a, (a, b), d, e, (i, j), k),则该广义表的长为________________。GetHead(GetTail(F))= _______________。 2.一个n*n的对称矩阵,如果以行或列为主序压缩存放入内存,则需要 个存储单元。 3.有稀疏矩阵如下: 0 0 5 7 0 0 -3 0 0 0 4 0 0 2 0 它的三元组存储形式为: 。 三、综合题(共 22 分)。 1.(共8分)稀疏矩阵如下图所示,描述其三元组的存储表示,以及转置后的三元组表示。 0 -3 0 0 0 4 0 6 0 0 0 0 0 0 7 0 15 0 8 0 转置前(4分): 转置后(4分): 2. (共14分)稀疏矩阵M的三元组表如下,请填写M的转置矩阵T的三元组表,并按要求完成算法。 (1)写出M矩阵转置后的三元组存储(6分): M的三元组表: T的三元组表: i j e 2 1 3 3 2 4 4 2 5 4 3 5 5 1 6 5 3 6 i j e (2)如下提供了矩阵采用三元组存储时查找指定行号(m)和列号(n)元素值的算法框架,将代码补充完整(每空2分,共8分)。 typedefstruct{ inti,j; ElemType e; }Triple; typedefstruct{ Triple data[MAXSIZE+1]; //data[0]未用 intmu,nu,tu; //矩阵的行数,列数和非零元的个数 }TSMatrix; voidFind_TSMatrix(TSMatrix M, int m, int n, ElemType&e) //M为要查找的稀疏矩阵三元组存储,m为要查找的元素的行号,n为列号,e为查找后得到的值。 { for ( i=1 ; i<=M.tu ;i++) if( && ) { e=M.data[i].e; ; } if( ) e=0; }

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值