数据结构 数组与树相关知识点与作业

数组和树及其应用

树和二叉树的建立和应用 【实验目的】

  1. 熟练掌握树的基本概念、二叉树的基本操作及在链式存储结构上的实现。
  2. 重点掌握二叉树的生成、遍历及求深度等算法。
  3. 掌握二叉树的线索化及线索二叉树的遍历算法;掌握赫夫曼树的含义及其应用。
  4. 掌握运用递归方式描述算法及编写递归C程序的方法,提高算法分析和程序设计能力。 【实验要求】 1.认真阅读和掌握和本实验相关的教材内容。 2.编写完整程序完成下面的实验内容并上机运行。 第一节 知识准备
    树型结构是一类非常重要的非线性结构,是《数据结构》课程中的重点和难点之一。掌握好本章的知识,对后续章节的理解和把握起着非常重要的作用。 一、
    树和二叉树的基本概念 如图6-1所示是一棵由n=13个结点构成的一棵树。
  5. 树中A称为树的根结点,它是唯一的且没有前驱。特别地,当树中一个结点也没有时,称该树为空树。
  6. A有3个后继结点B、C、D称为A的孩子,或说A有三棵子树,A的度就是3。结点B、C、D、E、F、G、H、I、J、K、L、M的度分别就是2、1、3、0、2、0、0、0、1、0、0、0
  7. 结点E、G、H、I、K、L、M的度都是0,凡是树中度为0的结点称为叶子或终端结点。
  8. 结点A、B、C、D、F、J的度都大于0,度大于0的结点称为分支结点或非终端结点。特别地,当树中只有一个结点A时,A既是根结点又是终端结点。
  9. 树中所有结点的度的最大值是3,该最大值称为树的度。图6-1所示树的度就是3。
  10. 树的最大层数为四层,所以树的深度为4。
  11. 树中结点B、C、D有相同的前驱结点A,结点E、F有相同的前驱结点B,等等,称A为B、C、D的双亲,B为E、F的双亲。反之又称B、C、D为A为孩子结点。B、C、D互为兄弟结点,E、F互为兄弟结点,等等。
  12. 树中除根结点外其余每个结点都有唯一的双亲结点,但可以有多个孩子结点,表现为一种一对多的非线性关系。
  13. 树中同一层各结点从左至右看成是有序的(即不能互换),则称该树为有序树,否则称为无序树。
  14. 若一棵有序树中每个结点的度都小于或等于2,则称该树为二叉树。二叉树是一类非常特殊且重要的树,有许多重要特性和应用,要很好掌握。在二叉树中要严格区分左右子树。
  15. m(m≥0)棵互不相交的树的集合称为森林。 二、树的基本性质
  16. 树中的结点数等于所有结点的度数加1。
  17. 度为k的树中第i层上至多有ki-1个结点(i≥1)。
  18. 深度为h的k叉树至多有 个结点。
  19. log k (n(k-1)+1) 具有n个结点的k叉树的最小深度为
  20. 如果一棵树有n1个度数为1的结点,n2个度数为2的结点,…,nm个度数为m的结点,则终端结点数n0= n2+n3+…+nm+1。 二、 二叉树的基本性质 二叉树是另外一种树型结构,其特点是每个结点至多两棵子树,并且子树有左右之分,不能随意颠倒顺序。二叉树有如下基本性质:
  21. 二叉树中第i层至多有 个结点(i≥1)。
  22. 在深度为k的二叉树中结点总数最多为 。
  23. 对任意一棵二叉树T,如果其终端结点数为 ,度为2的结点数为 ,则 。
  24. 对任何一棵有n个结点的完全二叉树或满二叉树中编号为i的结点(1≤i≤n,n≥1)有: (1) n/2」,即2i≤n,则编号为i的结点为分支结点,否则为终端结点若i≤ (2)
    若n为奇数,则每个分支结点都既有左孩子,也有右孩子;若n为偶数,则编号最大的分支结点(编号为n/2)只有左孩子,没有右孩子,其余分支结点左、右孩子都有。
    (3) 若编号为i的结点有左孩子,则左孩子结点的编号为2i;若编号为i的结点有右孩子,则右孩子结点的编号为2i+1。 (4)
    i/2」,换句话说,当i为偶数时,其双亲结点的编号为i/2,它是双亲结点的左孩子,当i为奇数时,其双亲结点的编号为(i-1)/2,它是双亲结点的右孩子。除树根结点外,若一个结点的编号为i,则它的双亲结点的编号为
    (5) 具有n(n>og 2 n」+1。 或 0)个结点的完全二叉树的深度为「log 2(n+1) 四、 二叉树的存储结构及线索二叉树
  25. 顺序存储结构 采用顺序存储结构存储一棵二叉树时,必须首先对该树中每个结点进行编号,树中各结点的编号应与等深度的满二叉树中对应位置上结点的编号相同,然后以各结点的编号为下标,将各结点的值存储到一维数组的对应下标单元中。
    特别地,当二叉树是一单支树时(即树中无度为2的结点),则采用顺序存储结构存储有n个结点的二叉树需要2k-1个存储分量。空间浪费十分巨大。所以,通常采用链式存储结构存储二叉树。
  26. 链式存储结构 根据二叉树的特点,每个结点有一个双亲(根结点除外)和至多两个称为左、右孩子的结点,
  27. 线索二叉树 在二叉树中按某种遍历顺序,求某给定结点的前驱或后继较难,必须对二叉树进行遍历,这将浪费大量的运算时间。由于在二叉链表表示的二叉树中大量的终端结点的左右指针域都为空,浪费较大,将这些域用来指向结点的前驱或后继,就形成了线索二叉树。将二叉树转化为线索二叉树的过程,叫线索化。
  28. 二叉树的运算 根据对二叉树的定义以及对二叉树采用链式存储结构,有关二叉树的运算如下 (1) InitBiTree(&T) 构造空二叉树T。 (2) CreateBiTree(&T,definition) 按definition构造二叉树T。 (3)
    ClearBiTree(&T) 将二叉树T清为空树。 (4) BiTreeEmpty(T) 判定二叉树T是否为空二叉树。 (5)
    BiTreeDepth(T) 求二叉树T的深度。 (6) Root(T) 取二叉树T的根。 (7)
    Parent(T,e):求二叉树T中结点e的双亲结点。 (8)
    LeftChild(T,e):求二叉树T中结点e的左孩子结点。若e无左孩子,则返回“空”。 (9)
    RightChild(T,e):求二叉树T中结点e的右孩子结点。若e无右孩子,则返回“空”。 (10)
    DeleteChild(T,p,LR):根据LR为0或1,删除二叉树T中p所指结点的左或右子树。 (11)
    PreOrderTraverse(T,Visit()):先序遍历二叉树T (12)
    InOrderTraverse(T,Visit()):中序遍历二叉树T (13)
    PostOrderTraverse(T,Visit()):中序遍历二叉树T (14)
    LevelOrderTraverse(T,Visit()):层序遍历二叉树T
    二叉树的运算较多,我们在将第二节实验中讨论有关二叉树运算的算法。其它运算的算法及C语言源程序由读者自行设计,并上机调试。 五、
    树的存储结构及运算
  29. 树的存储结构 (1) 定长结点的多重链表 (2) 取树的度数作为每个结点的指针域数。由于树中可能很多结点的度都小于甚至有的远远小于树的度,这些结点的部分指针域为空,造成存储空间的极大浪费,存储密度较小。
    (3) 不定长结点的多重链表 (4)
    按每个结点的度数来确定指针域数,另加一个度数域。其存储密度较定长结点多重链表有很大提高,但运算和操作不方便。 (5)
    二叉链表表示法(又称子女兄弟表示法) (6)
    每个结点均设两个指针域,第一个指针域指向该结点的最左孩子结点,第二个指针域指向该结点的右兄弟。利用这种存储结构便于实现各种树的运算,
  30. 树的运算 根据对树的定义以及对树采用二叉链表存储结构,有关树的运算如下: (1) InitBiTree(&T) 构造一棵空树T (2) DestroyBiTree(&T) 销毁树T (3) CreateBiTree(&T,definition)
    按definition构造树T (4) ClearBiTree(&T) 将树T清为空树 (5) TreeEmpty(T)
    判定T是否为空树 (6) TreeDepth(T) 求树T的深度 (7) Root(T) 取树T的根。若T为空树,则返回“空”
    (8) Parent(T,cur_e) 取树T中结点cur_e的双亲结点 (9) LeftChild(T,cur_e)
    取树T中结点cur_e的最左孩子,无,则返回“空” (10) RightSibling(T,cur_e)
    取树T中结点cur_e的右兄弟,无,则返回“空” (11) InsertChild(&T,&p,i,c)
    在树T中插入c为p指结点的第i棵子树 (12) DeleteChild(&T,&p,i) 删除树T中p指结点的第i棵子树 (13)
    TraverseTree(T,Visit()) 按某种次序访问树T的每个结点一次且仅一次 六、 赫夫曼树
    从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径,路径上的分支数目称为路径长度。树的路径长度是指从树根到每一结点的路径长度之和;树的带权路径长度是指树中所有终端结点的带权路径长度之和,记为
    。给定一组权值{w1,w2,……,wn},构造一棵有n个叶结点的二叉树,每个叶结点的权为wi,则其中
    WPL最小的二叉树称为最优二叉树或赫夫曼树。 第二节 二叉树的基本运算实验 【问题描述】
    二叉树采用二叉链表作存储结构,试编程实现二叉树的如下基本操作:
  31. 按先序序列构造一棵二叉链表表示的二叉树T;
  32. 对这棵二叉树进行遍历:先序、中序、后序以及层次遍历序列,分别输出结点的遍历序列;
  33. 求二叉树的深度/结点数目/叶结点数目;
  34. 将二叉树每个结点的左右子树交换位置。
【数据描述】
    //- - - - - - 二叉树的二叉链表存储表示 - - - - - - -
typedef struct BiTNode{
  TElemType        data;
  Struct BiTNode   * lchild, * rchild;  //左右孩子指针
}BiTNode, * BiTree;
【算法描述】
1. 建立一棵二叉树
Status CreateBiTree(BiTree &T)
//按先序次序输入二叉树中结点的值(一个字符),#字符表示空树,
//构造二叉链表表示的二叉树T。
scanf(&ch);
if (ch=='#') T=NULL;
else {
    if (!(T=(BiTNode *) malloc(sizeof(BiTNode)))) exit (OVERFLOW);
    T->data = ch;              //生成根结点
    CreateBiTree(T->lchild);   //生成左子树
    CreateBiTree(T->rchild);   //生成右子树
}
return OK;
}//CreateBiTree

2. 先序遍历二叉树递归算法
Status PreOrderTraverse(BiTree T,Status(* Visit)(TElemType e)){
//采用二叉链表存储结构,Visit是对数据元素操作的应用函数,
//先序遍历二叉树T,对每个结点调用函数Visit一次且仅一次。
//一旦visit()失败,则操作失败。
if (T){
   if (Visit(T->data))
if (PreOrderTraverse(T->rchild,Visit))  return OK;
return ERROR;
}else  return OK;
}// PreOrderTraverse

3. 中序遍历的递归算法
Status InOrderTraverse(BiTree T,Status(* Visit)(TElemType e)){
if (T){
   if (Visit(T->data))
if (InOrderTraverse(T->rchild,Visit)) return OK;
return ERROR;
}else  return OK;
}// InOrderTraverse

4. 后序遍历递归算法
Status PostOrderTraverse(BiTree T,Status(* Visit)(TElemType e)){
if (T){
   if (Visit(T->data))
if (PreOrderTraverse(T->rchild,Visit))  return OK;
return ERROR;
}else  return OK;
}// PreOrderTraverse

5. 中序遍历非递归算法之一
Status InOrderTraverse(BiTree T, Status (* Visit)(TElemType e)) {
  InitStack(S);
  Push(S,T);                   //根指针进栈
  While (!StackEmpty(S)) {
While (GetTop(S,p) && p) Push(S,p->lchild); //向左走到尽头
Pop(S,p);                 //空指针退栈
If (!StackEmpty(S)) {     //访问结点,向右一步
   Pop(S,p);
   If (!Visit(p->data))  return ERROR;
   Push(S,p->rchild);
}//if
}//while
return OK;
    }//InOrderTraverse
    
6. 中序遍历非递归算法之二
Status InOrderTraverse(BiTree T, Status (* Visit)(TElemType e)) {
  //采用二叉链表存储结构,Visit是对数据元素操作的应用函数。
  //中序遍历二叉树T的非递归算法,对每个数据元素调用函数Visit。
  InitStack(S);
  p=T;
  While (p‖!StackEmpty(S)) {
if (p) {Push(S,p);  p=p->lchild;} //根指针进栈,遍历左子树
else {                //根指针退栈,访问根结点,遍历右子树
Pop(S,p); 
if (!Visit(p->data))  return ERROR;
   p=p->rchild);
}//else
}//while
return OK;
    }//InOrderTraverse

7. 层次遍历二叉树的非递归算法
Status LevelOrder(BiTree T){
  //按层次遍历二叉树T, Q为队列
  InitQueue(Q);
  If (T!=NULL){// 若树非空
      EnQueue(Q,T);//根结点入队列
      While (!QueueEmpty(Q)){
        DeQueue(Q,b);     //队首元素出队列
      Visit(b->data);   //访问结点
        If (b->lchild!=NULL) EnQueue(Q,b->lchild);//左子树非空,则入队列
        If (b->rchold!=Null) EnQueue(Q,b->rchild);//右子树非空,则入队列
      }//while
  }//if
}LevelOrder


8. 求二叉树的深度
int depth(BiTree T){
//若T为空树,则深度为0,否则其深度等于左子树或右子树的最大深度加1
   if (T==NULL) return 0;
   else {dep1=depth(T->lchild);
         dep2=depth(T->rchild);
         return dep1>dep2?dep1+1:dep2+1;
}
     }C源程序】
#include <stdio.h>
#include <stdlib.h>
#define MAX 20
#define NULL 0
typedef char TElemType;
typedef int  Status;
typedef struct BiTNode
{    TElemType data;
    struct BiTNode *lchild,*rchild;
} BiTNode,*BiTree;
Status CreateBiTree(BiTree *T) ///BiTNode T
{    char ch;
    ch=getchar();
    if(ch=='#')(*T)=NULL;///#代表空指针
    else
    {
        (*T)=(BiTree)malloc(sizeof(BiTNode));
        (*T)->data=ch;///生成根结点
        CreateBiTree(&(*T)->lchild);///构造左子树
        CreateBiTree(&(*T)->rchild);///构造右子树
    }
    return 1;
}
///先序遍历输出
void PreOrder(BiTree T)
{    if(T)
    {    printf("%2c",T->data);///T->data==(*T).data
        PreOrder(T->lchild);///先序遍历左子树
        PreOrder(T->rchild);///先序遍历右子树
    }
}
///层次遍历二叉树T,从第一层开始,每层从左到右
void LevleOrder(BiTree T)
{    BiTree Queue[MAX],b;
    ///用一维数组表示队列,front和rear分别表示队首和队尾指针
    int front,rear;
    front=rear=0;
    if(T) ///若树非空
    {    Queue[rear++]=T;///根结点入队列
        while(front!=rear) ///当队列非空
        {
            b=Queue[front++];///队首元素出队列,并访问这个结点
            printf("%2c",b->data);
            if(b->lchild!=NULL)
            {    Queue[rear++]=b->lchild;
                ///左子树非空,则入队列
            }
            if(b->rchild!=NULL)
            {
                Queue[rear++]=b->rchild;
                ///右子树非空,则入队列
            }
        }
    }
}
///求二叉树的深度
int depth(BiTree T){
    int dep1,dep2;
    if(T==NULL)  return 0;
    else
    {
        dep1=depth(T->lchild);
        dep2=depth(T->rchild);
        return dep1>dep2?dep1+1:dep2+1;
    }
}
int main()
{    BiTree T=NULL;///(开始定义的是*T)此时T为地址
    printf("\n(Create a Binary Tree )建立一棵二叉树T:\n");
    CreateBiTree(&T);///建立一棵二叉树
    printf("\nThe preorder(先序序列为)is:\n");
    PreOrder(T);
    printf("\nThe levle order(层次序列为)is:\n");
    LevleOrder(T);
    printf("\nThe depth(深度)is:%d\n",depth(T));
    getchar();
    return 0;
}

建立如图所示二叉树,输入结点↙, 得出先序输出为: 层次遍历输出为: 深度输出为:
在这里插入图片描述

【说明】

  1. 按先序次序输入二叉树中结点的值,用’#'表示空树,对每一个结点应当确定其左右子树的值(为空时必须用特定的空字符占位),故执行此程序时,最好先在纸上画出你想建立的二叉树,每个结点的左右子树必须确定,若为空,则用特定字符标出,然后再按先序输入这棵二叉树的字符序列;
  2. 为了简化程序的书写量,以及程序的清晰性,对结点的访问以一条输出语句表示,若有更复杂的或多种访问,可以将结点的访问编写成函数,然后通过函数指针进行函数的调用。读者若有兴趣,可自行编写。
  3. C语言函数参数传递,都是“传值”的方式,故在设计函数时,必须注意参数的传递,若想通过函数修改实际参数的值,必须对指针变量作参数。具体设计时,读者一定要把指针变量、指针变量指向的值等概念弄清楚;
  4. 对于其他算法,请读者参照示例,自行编程完成,以加深学习体会。 【实验题】 在本节的算法描述中已给出了二叉树的中序遍历和后序遍历算法,请根据此种算法加入C语言源程序,实现中序遍历和后序遍历功能,并上机调试通过。
    【思考题】 在本节的算法描述中已给出了二叉树的非递归算法,请根据此种算法编写C语言源程序,实现下述功能,并上机调试通过。
  5. 按中序顺序建立一棵二叉树;
  6. 用非递归方式遍历二叉树(先序、中序或后序),输出遍历序列。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值