#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树

树和二叉树的定义

树的定义

#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第1张图片
  • 树还可以表示为嵌套集合(类似韦恩图)、广义表、凹入表示(类似书的目录)。

树的基本术语

#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第2张图片
  • 树的深度:树中结点的最大层次。

  • 有序树:树中结点的各子树从左至右有次序。

#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第3张图片

二叉树的定义

使用二叉树的原因

二叉树的规律性强,且所有的树都可以转化为唯一对应的二叉树,实现较为简易的运算。

二叉树的定义和特点

#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第4张图片

二叉树和树是不同的概念

#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第5张图片
  • 虽然二叉树和树的概念不同,但是有关树的术语对于二叉树都适用。

二叉树的性质和存储结构

二叉树的性质1、2、3

  • 第i层上至少有1个结点。

  • 深度为k时至少有k个结点。

#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第6张图片

两种特殊形式的二叉树

满二叉树

#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第7张图片

完全二叉树

#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第8张图片
#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第9张图片

完全二叉树的性质(二叉树的性质4、5)

#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第10张图片

二叉树的存储结构

二叉树的顺序存储

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

#define MAXSIZE 100
Typedef TElemType SqBiTree[MAXSIZE]
SqBiTree bt;
  • 缺点:若二叉树为右单支树,则存储密度过低,空间浪费大;

  • 适用于存储满二叉树和完全二叉树。

二叉树的链式存储

  • 图示:

#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第11张图片
typedef struct BiNode{//二叉链表
    TElemType data;
    struct BiNode *lchild,*rchild;//左右孩子指针
}BiNode,*BiTree;
#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第12张图片
typedef struct TriTNode{//三叉链表,用于在某个结点寻找双亲
    TelemType data;
    struct TriNode *lchild,*parent,*rchild;//增加了指向双亲的指针
}TriTNode,*TriTree;

遍历二叉树和线索二叉树

遍历二叉树

由遍历二叉树确定遍历序列

遍历

  • 定义:顺着某一条搜索路径巡访问二叉树中的结点,使得每个结点均被访问一次,而且仅被访问一次(又称周游)。

  • 目的:得到树中所有结点的一个线性序列。

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

  • 方法:共三种。设访问根结点为D,遍历左子树为L,遍历右子树为R,且规定先左后右,则又三种情况:DLR(先序遍历)、LDR(中序遍历)、LRD(后序遍历)。

遍历二叉树算法描述

#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第13张图片

由遍历序列确定二叉树

已知先序和中序序列求而二叉树

#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第14张图片

已知中序和后序序列求二叉树

#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第15张图片

二叉树递归遍历算法

先序遍历算法

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

中序遍历算法

Status InOrderTraverse(BiTree T){
    if(T==NULL) return OK;
    else{
        PreOrderTraverse(T->lchild);
        visit(T);
        PreOrderTraverse(T->rchild);
    }
}

后序遍历算法

Status PostOrderTraverse(BiTree T){
    if(T==NULL) return OK;
    else{
        PreOrderTraverse(T->lchild);
        PreOrderTraverse(T->rchild);
        visit(T);
    }
}

遍历算法的分析

#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第16张图片
#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第17张图片

二叉树非递归算法

中序遍历的非递归算法

Status InOrderTraverse(BiTree T){
    BiTree p;
    InitStack(S);
    P=T;
    while(p||!StackEmpty(S)){
        if(p){
            Push(S,p);
            p=p->lchild;
        }
        else{
            Pop(S,q);
            printf("%c",q->data);
            p=q->rchild;
        }
    }
    return OK;
}

二叉树的层次遍历

  • 层次遍历:对于一棵二叉树,从根结点开始,按从上到下、从左到右的顺序访问每一个结点。使用队列来实现。

typedef struct{
    BTNode data[MAXSIZE];//存放队中元素
    int front,rear;//队头和队尾指针
}SqQueue;//顺序循环队列类型 
void LevelOrder(BTNode *b){
    BTNode *p;
    SqQueue *qu;
    InitQueue(qu);//初始化队列
    enQueue(qu,b);//根结点指针进入队列
    while(!QueueEmpty(qu)){//队不为空,则循环
        deQueue(qu,p);//出队结点p
        printf("%c",p->data);//访问结点p
        if(p->lchild!=NULL) enQueue(qu,p->lchild);//有左孩子时将其进队
        if(p->rchild!=NULL) enQueue(qu,p->rchild);//有右孩子时将其进队
    }
}

二叉树遍历算法的应用

二叉树的建立

  • 按先序遍历序列建立二叉树的二叉链表,按顺序读入字符ch:ABC##DE#G##F###

Status CreateBiTree(BiTree &T){
    cin>>ch;
    if(ch=="#") T=NULL;
    else{
        if(!(T=new BiTree)) exit(OVERFLOW);//开辟出链表空间
        T->data=ch;//生成根节点
        CreateBiTree(T->lchild);//构造左子树
        CreateBiTree(T->rchild);//构造右子树
    }
    return OK;
}

复制二叉树

int Copy(BiTree T,BiTree &NewT){
    if(T=NULL){
        NewT=NULL;return 0;
    }
    else{
        NewT=New BiTNode;
        NewT->data=T->data;
        Copy(T->lchild,NewT->lchild);
        Copy(T->rchild,NewT->rchild);
    }
}

计算二叉树深度

int Depth(BiTree T){
    if(T==NULL) return 0;
    else{
        m=Depth(T->lchild);
        n=Depth(T->rchild);
        if(m>n) return(m+1);
        else return(n+1);
    }   
} 

计算二叉树结点总数

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

计算二叉树叶子总数

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

线索二叉树

  • 用于寻找特定遍历序列中二叉树结点的前驱和后继;

  • 利用二叉链表中的空指针域,左空则将其指向前驱,右空则将其指向后继;

  • 改变指向的指针称为线索,加上线索的二叉树称为线索二叉树,对二叉树按某种遍历次序使其变为线索二叉树的过程叫线索化

  • 为区分lchild指针和rchild指针到底是指向孩子还是指向前驱后继,对二叉链表中每个结点增设两个标志域ltag和rtag:

#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第18张图片
typedef struct BiThrNode{
    int data;
    int ltag,rtag;
    struct BiThrNode *lchild,*rchild;
}BiThrNode,*BiThrTree;
  • 在中序遍历的情况下,序列头尾结点会各剩下一个空指针域,为避免这种悬空态,增设一个头结点,对于这个头结点来说:

树和森林

树的存储结构

双亲表示法

  • 实现:定义结构数组,存放树的结点,每个结点含两个域;

  • 数据域:存放结点本身信息;

  • 双亲域:指示本结点的双亲结点在数组中的位置;

  • 特点:找双亲容易,找孩子难。

#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第19张图片
typedef  struct PTNode{//结点结构定义
    TElemType data;
    int parent;//双亲位置域
}PTNode;
#define MAX_TREE_SIZE 100
typedef struct{//树结构定义
    PTNode nodes[MAX_TREE_SIZE];
    int r,n;//根结点的位置和结点个数
}PTree;

孩子链表

  • 实现:用单链表存储指向每一个结点的指针,每个非叶子结点的指针又作为一个链表的头指针,后接它的孩子结点的指针,如下:

#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第20张图片

孩子兄弟表示法

  • 又称二叉树表示法、二叉链表表示法

  • 实现:用二叉链表作树的存储结构,链表中每个结点的两个指针域分别指向其第一个孩子结点下一个兄弟结点

typedef struct CSNode{
    ElemType data;
    struct CSNode *firstchild,*nextsibling;
}CSNode,*CSTree;
#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第21张图片

树与二叉树的转换

将树转换成二叉树

  • 树变二叉树:兄弟相连留长子

  1. 加线:在兄弟之间加一条线

  1. 抹线:对每一个结点,除了其左孩子外,去除与其余孩子之间的关系

  1. 旋转:以树的根结点为轴心,将横线顺时针转45°

#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第22张图片

将二叉树转换成树

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

  1. 加线:若某结点是双亲结点的左孩子,则将其右孩子、右孩子的右孩子、......沿分支找到的所有右孩子,都与该结点的双亲用线连起来

  1. 抹线:抹掉原二叉树中双亲与右孩子之间的连线

  1. 调整:将结点按层次排列,形成树结构

#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第23张图片

森林与二叉树的转换

将森林转换成二叉树

  • 树变二叉根相连

  1. 将各棵树分别转换成二叉树

  1. 将每棵树的根结点用线相连

  1. 以第一棵树根结点为二叉树的根,再以根结点为轴心,顺时针旋转,构成二叉树型结构

#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第24张图片

将二叉树转换成森林

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

  1. 抹线:将二叉树中根结点与其右孩子连线、及沿右分支搜索到的所有右孩子间连线全部抹掉,使之变成孤立的二叉树

  1. 还原:将孤立的二叉树还原成树

#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第25张图片

树与森林的遍历

树的遍历

先序、后序、按层次

森林的遍历

  • 先序遍历:依次从左至右对森林中的每一课树进行先序遍历

  • 后序遍历:依次从左至右对森林中的每一课树进行后序遍历

哈夫曼树及其应用

  • 引例:

#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第26张图片
#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第27张图片

哈夫曼树的基本概念

  • 路径:从树中一个结点到另一个结点之间的分支构成这两个结点间的路径。

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

  • 树的路径长度:从树根到每一个结点的路径长度之和,记作:TL

#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第28张图片
  • 权(Weight):将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。

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

  • 树的带权路径长度:树中所有叶子结点的带权路径长度之和,

#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第29张图片
#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第30张图片
#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第31张图片
  • 满二叉树不一定是哈夫曼树;

  • 哈夫曼树中权越大的叶子离根越近;

  • 具有相同带权结点的哈夫曼树不唯一。

哈夫曼树的构造算法

哈夫曼算法

#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第32张图片
#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第33张图片
  • 在哈夫曼算法中,初始时有n棵二叉树,要经过n-1次合并最终形成哈夫曼树。

  • 经过n-1次合并产生n-1个新结点,且这n-1个新结点都是具有两个孩子的分支结点。

  • 可见,哈夫曼树中共有2n-1个结点,且其所有的分支结点的度均不为1.

哈夫曼树构造算法的实现

#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第34张图片
typedef struct{
    int weight;
    int parent,lch,rch;
}HTNode,*HuffmanTree;
void CreatHuffmanTree(HuffmanTree HT,int n){//构造哈夫曼树——哈夫曼算法
    if(n<=1) return;
    m=2*n-1;//数组共2n-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++){//合并产生n-1个结点————构造哈夫曼树,从第n+1个空间开始暂存合并的结点权值
        Select(HT,i-1,s1,s2);//在HT[k](1<=k<=i-1)中选择两个其双亲域为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的权值为左右孩子权值之和
    }
}
#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第35张图片

哈夫曼编码

哈夫曼编码的概念

#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第36张图片
#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第37张图片
#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第38张图片
#笨鸟先飞# 数据结构与算法基础 课程笔记 第五章 树和二叉树_第39张图片

哈夫曼编码的算法实现

void CreateHuffmanCode(HuffmanTree HT,HuffmanCode &HC,int n){//从叶子到根逆向求每个字符的哈夫曼编码,存储在编码表HC中
    HC=new char*[n+1];//分配n个字符编码的头指针矢量
    cd=new char[n];//分配临时存放编码的动态数组空间
    cd[n-1]='\0';//编码结束符
    for(i=1;i<=n;i++){//逐个字符求哈夫曼编码
        start=n-1;
        c=i;
        f=HT[i].parent;
        while(f!=0){//从叶子结点开始向上回溯,直到根结点
            start--;//回溯一次start向前指一个位置
            if(HT[f].lchild==c) cd[start]='0';//结点c是f的左孩子,则生成代码0
            else cd[start]='1';//结点c是f的右孩子,则生成代码1
            c=f;
            f=HT[f].parent;//继续向上回溯
        }//求出第i个字符的编码
        HC[i]=new char[n-start];//为第i个字符串编码分配空间
        strcpy(HC[i],&cd[start]);//将求得的编码从临时空间cd复制到HC的当前行中
    }
    delete cd;//释放临时空间
}        

文件的编码和解码

  • 编码:

  1. 输入各字符及其权值

  1. 构造哈夫曼树HT[i]

  1. 进行哈夫曼编码

  1. 查HC[i],得到各字符的哈夫曼编码

  • 解码:

  1. 构造哈夫曼树

  1. 依次读入二进制码

  1. 读入0,则走向左孩子;读入1,则走向右孩子

  1. 一旦到达某叶子时,即可译出字符

  1. 然后再从根出发继续译码,直到结束

你可能感兴趣的:(笨鸟先飞,数据结构)