zoukankan      html  css  js  c++  java
  • 4. 树与二叉树 (算法和数据结构笔记)

    4. 树与二叉树

    树的存储结构

    双亲表示法

    孩子表示法(其改进为"双亲孩子表示法")

    孩子兄弟表示法

     

    双亲表示法

    思想: 假设以一组连续空间存储树的结点,同时在每个结点中,附设一个指示器指示其双亲结点在数组中的位置。

    数的结点结构:

    /* 树的双亲表示法结点结构定义 */

    #define MAX_TREE_SIZE 100

    typedef int TElemType; /* 树结点的数据类型,目前暂定为整型 */

     

    typedef struct PTNode /* 结点结构 */

    {

    TElemType data; /* 结点数据 */

    int parent; /* 双亲位置 */

    } PTNode;

     

    typedef struct /* 树结构 */

    {

    PTNode nodes[MAX_TREE_SIZE]; /* 结点数组 */

    int r, n; /* 根的位置和结点数 */

    } PTree;

     

    ② 孩子表示法

    思想: 每个结点有多个指针域,其中每个指针指向一棵子树的根结点,我们把这种方法叫做多重链表表示法

    方案一: 每个结点的指针域的个数就等于树的度(缺点: 浪费空间)

    方案二: 每个结点指针域的个数等于该结点的度(缺点: 初始化和维护起来难度大)

    方案三: 针对上述两个方案缺点改进的孩子表示法

    思想: 把每个结点的孩子结点排列起来,以单链表作存储结构,则n个结点有n个孩子链表,如果是叶子结点则此单链表为空。然后n头指针又组成一个线性表,采用顺序存储结构,存放进一个一维数组中.

    改进的孩子表示法有两种结点结构:

    1. 孩子链表的孩子节点

    1. 表头数组的表头结点

    ※ 方案三还有一种改进, 就是在表头结点里加一个存放双亲下标的成员, 如上面的右图所示, 这就是进一步完善的"双亲孩子表示法".

    /* 双亲孩子表示法的结构定义 */

    #define MAX_TREE_SIZE 100

     

    typedef struct CTNode /* 孩子链表的孩子结点 */

    {

    int child;    //孩子节点的下标

    struct CTNode *next;    //指向下一个孩子节点的指针

    } *ChildPtr;

     

    typedef struct /*表头数组的表头结点 */

    {

    TElemType data;

    int parent; //存放双亲的下标

    ChildPtr firstchild;

    } CTBox;

     

    typedef struct /* 整个树的结构 */

    {

    CTBox nodes[MAX_TREE_SIZE]; /* 表头结点组成的表头数组 */

    int r,n; /* r:根的下标; n: 结点数 /

    } CTree;

     

    1. 孩子兄弟表示法

    思想: 任意一棵树,它的结点的第一个孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的。因此,我们设置两个指针,分别指向该结点的第一个孩子和此结点的右兄弟:

    /* 树的孩子兄弟表示法结构定义 */

    typedef struct CSNode

    {

    TElemType data;

    struct CSNode *firstchild,

    *rightsib;

    } CSNode, *CSTree;

     

    ● 二叉树的顺序结构实现

    //起始部分

    #include "stdio.h"

    #include "stdlib.h"

    #include "io.h"

    #include "math.h"

    #include "time.h"

     

    #define OK 1

    #define ERROR 0

    #define TRUE 1

    #define FALSE 0

     

    #define MAXSIZE 100 /* 存储空间初始分配量 */

    #define MAX_TREE_SIZE 100 /* 二叉树的最大结点数 */

     

    typedef int Status;        /* Status是函数的类型,其值是函数结果状态代码,如OK*/

    typedef int TElemType; /* 树结点的数据类型,目前暂定为整型 */

    typedef TElemType SqBiTree[MAX_TREE_SIZE]; /* 0号单元存储根结点; SqBiTree(Sequential BiTree) */

    /*    例如:SqBiTree bt;

        意味着:

        TELemtype bt[100]; int bt[100] */

     

    //定义一个Position结构体, 成员包括某结点所在的层次以及所在层次的序号

    typedef struct

    {

        int level,order; /* 结点的层,本层序号(按满二叉树计算) */

    }Position;

     

    TElemType Nil=0; /* 设整型以0为空 */

     

    //打印各结点数据域的内容

    Status visit(TElemType c)

    {

        printf("%d ",c);

        return OK;

    }

     

    /* 构造空二叉树T。因为T是固定数组,不会改变,故不需要写成&T */

    Status InitBiTree(SqBiTree T)    

    //下面的操作是: 传入一个顺序结构存储的树T, 然后打印树的各个结点的值

    {

        int i;

        for(i=0;i<MAX_TREE_SIZE;i++)

            T[i]=Nil; /* 初值为0 */

        return OK;

    }

     

    /* 构造顺序存储的二叉树T, 这里按层序遍历的次序输入二叉树中各个结点的值(字符型或整型) */

    Status CreateBiTree(SqBiTree T)

    {

        int i=0;

        while(i<10)    //下面是通过循环的方式给树的各节点赋值

        {

            T[i]=i+1;

            

            if(i!=0&&T[(i+1)/2-1]==Nil&&T[i]!=Nil)

            /* i!=0表示此结点(不空), T[(i+1)/2-1]==Nil表示无父节, T[i]!=Nil表示不是根 */

            {

                printf("出现无父节点的非根结点%d ",T[i]);

                exit(ERROR);

            }

            i++;

        }

        while(i<MAX_TREE_SIZE)

        {

            T[i]=Nil; /* 将空赋值给T的后面的结点 */

            i++;

        }

     

        return OK;

    }

     

    /* 初始条件: 二叉树T存在 */

    /* 操作结果: T为空二叉树,则返回TRUE,否则FALSE */

    #define ClearBiTree InitBiTree /* 在顺序存储结构中,两函数完全一样 */

     

    Status BiTreeEmpty(SqBiTree T)

    {

        if(T[0]==Nil) /* 根结点为空,则树空 */

            return TRUE;

        else

            return FALSE;

    }

     

    /* 初始条件: 二叉树T存在。操作结果: 返回T的深度 */

    //注意, i代表结点的个数, j代表T的深度, 一个满二叉树每层的分数为2(n-1)次幂

    int BiTreeDepth(SqBiTree T)

    {

    int i,j=-1;

    for(i=MAX_TREE_SIZE-1;i>=0;i--) /* 找到最后一个结点 */

    if(T[i]!=Nil)

    break;

    i++;

    do

    j++;

    while(i>=powl(2,j));/* 计算2j次幂 */

    return j;

    }

     

    /* 初始条件: 二叉树T存在 */

    /* 操作结果: T不空,e返回T的根,返回OK;否则返回ERROR,e无定义 */

    Status Root(SqBiTree T,TElemType *e)

    {

        if(BiTreeEmpty(T)) /* T*/

            return ERROR;

        else

        {    

            *e=T[0];

            return OK;

        }

    }

     

    /* 初始条件: 二叉树T存在,eT中某个结点(的位置) */

    /* 操作结果: 返回处于位置e(,本层序号)的结点的值 */

    TElemType Value(SqBiTree T,Position e)

    {

         return T[(int)powl(2,e.level-1)+e.order-2];

    }

     

    /* 初始条件: 二叉树T存在,eT中某个结点(的位置) */

    /* 操作结果: 给处于位置e(,本层序号)的结点赋新值value */

    Status Assign(SqBiTree T,Position e,TElemType value)

    {

        int i=(int)powl(2,e.level-1)+e.order-2; /* 将层、本层序号转为矩阵的序号 */

        if(value!=Nil&&T[(i+1)/2-1]==Nil) /* 给叶子赋非空值但双亲为空 */

            return ERROR;

        else if(value==Nil&&(T[i*2+1]!=Nil||T[i*2+2]!=Nil)) /* 给双亲赋空值但有叶子(不空) */

            return ERROR;

        T[i]=value;

        return OK;

    }

     

    /* 初始条件: 二叉树T存在,eT中某个结点 */

    /* 操作结果: eT的非根结点,则返回它的双亲,否则返回"空" */

    TElemType Parent(SqBiTree T,TElemType e)

    {

        int i;

        if(T[0]==Nil) /* 空树 */

            return Nil;

        for(i=1;i<=MAX_TREE_SIZE-1;i++)

            if(T[i]==e) /* 找到e */

                return T[(i+1)/2-1];

        return Nil; /* 没找到e */

    }

     

    /* 初始条件: 二叉树T存在,eT中某个结点 */

    /* 操作结果: 返回e的左孩子。若e无左孩子,则返回"空" */

    TElemType LeftChild(SqBiTree T,TElemType e)

    {

        int i;

        if(T[0]==Nil) /* 空树 */

            return Nil;

        for(i=0;i<=MAX_TREE_SIZE-1;i++)

            if(T[i]==e) /* 找到e */

                return T[i*2+1];

        return Nil; /* 没找到e */

    }

     

    /* 初始条件: 二叉树T存在,eT中某个结点 */

    /* 操作结果: 返回e的右孩子。若e无右孩子,则返回"空" */

    TElemType RightChild(SqBiTree T,TElemType e)

    {

        int i;

        if(T[0]==Nil) /* 空树 */

            return Nil;

        for(i=0;i<=MAX_TREE_SIZE-1;i++)

            if(T[i]==e) /* 找到e */

                return T[i*2+2];

        return Nil; /* 没找到e */

    }

     

    /* 初始条件: 二叉树T存在,eT中某个结点 */

    /* 操作结果: 返回e的左兄弟。若eT的左孩子或无左兄弟,则返回"空" */

    TElemType LeftSibling(SqBiTree T,TElemType e)

    {

        int i;

        if(T[0]==Nil) /* 空树 */

            return Nil;

        for(i=1;i<=MAX_TREE_SIZE-1;i++)

            if(T[i]==e&&i%2==0) /* 找到e且其序号为偶数(是右孩子) */

                return T[i-1];

        return Nil; /* 没找到e */

    }

     

    /* 初始条件: 二叉树T存在,eT中某个结点 */

    /* 操作结果: 返回e的右兄弟。若eT的右孩子或无右兄弟,则返回"空" */

    TElemType RightSibling(SqBiTree T,TElemType e)

    {

        int i;

        if(T[0]==Nil) /* 空树 */

            return Nil;

        for(i=1;i<=MAX_TREE_SIZE-1;i++)

            if(T[i]==e&&i%2) /* 找到e且其序号为奇数(是左孩子) */

                return T[i+1];

        return Nil; /* 没找到e */

    }

     

    /* PreOrderTraverse()调用 */

    void PreTraverse(SqBiTree T,int e)

    {

        visit(T[e]);

        if(T[2*e+1]!=Nil) /* 左子树不空 */

            PreTraverse(T,2*e+1);

        if(T[2*e+2]!=Nil) /* 右子树不空 */

            PreTraverse(T,2*e+2);

    }

     

    /* 初始条件: 二叉树存在 */

    /* 操作结果: 先序遍历T*/

    Status PreOrderTraverse(SqBiTree T)

    {

        if(!BiTreeEmpty(T)) /* 树不空 */

            PreTraverse(T,0);

        printf(" ");

        return OK;

    }

     

    /* InOrderTraverse()调用 */

    void InTraverse(SqBiTree T,int e)

    {

        if(T[2*e+1]!=Nil) /* 左子树不空 */

            InTraverse(T,2*e+1);

        visit(T[e]);

        if(T[2*e+2]!=Nil) /* 右子树不空 */

            InTraverse(T,2*e+2);

    }

     

    /* 初始条件: 二叉树存在 */

    /* 操作结果: 中序遍历T*/

    Status InOrderTraverse(SqBiTree T)

    {

        if(!BiTreeEmpty(T)) /* 树不空 */

            InTraverse(T,0);

        printf(" ");

        return OK;

    }

     

    /* PostOrderTraverse()调用 */

    void PostTraverse(SqBiTree T,int e)

    {

        if(T[2*e+1]!=Nil) /* 左子树不空 */

            PostTraverse(T,2*e+1);

        if(T[2*e+2]!=Nil) /* 右子树不空 */

            PostTraverse(T,2*e+2);

        visit(T[e]);

    }

     

    /* 初始条件: 二叉树T存在 */

    /* 操作结果: 后序遍历T*/

    Status PostOrderTraverse(SqBiTree T)

    {

        if(!BiTreeEmpty(T)) /* 树不空 */

            PostTraverse(T,0);

        printf(" ");

        return OK;

    }

     

    /* 层序遍历二叉树 */

    void LevelOrderTraverse(SqBiTree T)

    {

        int i=MAX_TREE_SIZE-1,j;

        while(T[i]==Nil)

            i--; /* 找到最后一个非空结点的序号 */

        for(j=0;j<=i;j++) /* 从根结点起,按层序遍历二叉树 */

            if(T[j]!=Nil)

                visit(T[j]); /* 只遍历非空的结点 */

        printf(" ");

    }

     

    /* 逐层、按本层序号输出二叉树 */

    void Print(SqBiTree T)

    {

        int j,k;

        Position p;

        TElemType e;

        for(j=1;j<=BiTreeDepth(T);j++)

        {

            printf("第%d层: ",j);

            for(k=1;k<=powl(2,j-1);k++)

            {

                p.level=j;

                p.order=k;

                e=Value(T,p);

                if(e!=Nil)

                    printf("%d:%d ",k,e);

            }

            printf(" ");

        }

    }

     

    //主函数

    int main()

    {

        Status i;

        Position p;

        TElemType e;

        SqBiTree T;

        InitBiTree(T);

        CreateBiTree(T);

        printf("建立二叉树后,树空否?%d(1:0:) 树的深度=%d ",BiTreeEmpty(T),BiTreeDepth(T));

        i=Root(T,&e);

        if(i)

            printf("二叉树的根为:%d ",e);

        else

            printf("树空,无根 ");

        printf("层序遍历二叉树: ");

        LevelOrderTraverse(T);

        printf("前序遍历二叉树: ");

        PreOrderTraverse(T);

        printf("中序遍历二叉树: ");

        InOrderTraverse(T);

        printf("后序遍历二叉树: ");

        PostOrderTraverse(T);

        printf("修改结点的层号3本层序号2");

        p.level=3;

        p.order=2;

        e=Value(T,p);

        printf("待修改结点的原值为%d请输入新值:50 ",e);

        e=50;

        Assign(T,p,e);

        printf("前序遍历二叉树: ");

        PreOrderTraverse(T);

        printf("结点%d的双亲为%d,左右孩子分别为",e,Parent(T,e));

        printf("%d,%d,左右兄弟分别为",LeftChild(T,e),RightChild(T,e));

        printf("%d,%d ",LeftSibling(T,e),RightSibling(T,e));

        ClearBiTree(T);

        printf("清除二叉树后,树空否?%d(1:0:) 树的深度=%d ",BiTreeEmpty(T),BiTreeDepth(T));

        i=Root(T,&e);

        if(i)

            printf("二叉树的根为:%d ",e);

        else

            printf("树空,无根 ");

        

        return 0;

    }

     

     

    二叉树的链式存储结构(二叉链表)

    //起始部分

    #include "string.h"

    #include "stdio.h"

    #include "stdlib.h"

    #include "io.h"

    #include "math.h"

    #include "time.h"

     

    #define OK 1

    #define ERROR 0

    #define TRUE 1

    #define FALSE 0

     

    #define MAXSIZE 100 /* 存储空间初始分配量 */

     

    typedef int Status;        /* Status是函数的类型,其值是函数结果状态代码,如OK*/

     

    /* 用于构造二叉树 */

    int index=1;

    typedef char String[24]; /* 0号单元存放串的长度 */

    String str;    //相当于char str[24]

     

    Status StrAssign(String T,char *chars)    //字符串相互赋值的函数

    {

        int i;

        if(strlen(chars)>MAXSIZE)

            return ERROR;

        else

        {

            T[0]=strlen(chars);

            for(i=1;i<=T[0];i++)

                T[i]=*(chars+i-1);

            return OK;

        }

    }

     

    //访问结点的值

    typedef char TElemType;

     

    TElemType Nil=' '; /* 字符型以空格符为空 */

     

    Status visit(TElemType e)

    {

        printf("%c ",e);

        return OK;

    }

     

    //二叉树的二叉链表的结点结构的定义:

    typedef struct BiTNode /* 结点结构 */

    {

    TElemType data;        /* 结点数据 */

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

    }BiTNode,*BiTree;    //BiTNode代表结点, BiTree代表树

     

    /* 构造空二叉树T */

    Status InitBiTree(BiTree *T)

    {

        *T=NULL;

        return OK;

    }

     

    /* 初始条件: 二叉树T存在。操作结果: 销毁二叉树T */

    void DestroyBiTree(BiTree *T)

    {

        if(*T)

        {

            if((*T)->lchild) /* 有左孩子 */

                DestroyBiTree(&(*T)->lchild); /* 销毁左孩子子树 */

            if((*T)->rchild) /* 有右孩子 */

                DestroyBiTree(&(*T)->rchild); /* 销毁右孩子子树 */

            free(*T); /* 释放根结点 */

            *T=NULL; /* 空指针赋0 */

        }

    }

     

    /* 按前序输入二叉树中结点的值(一个字符) */

    /* #表示空树,构造二叉链表表示二叉树T*/

    //为了能让每个结点确认是否有左右孩子,我们对它进行了扩展, 即是将二叉树中每个结点的空指针引出一个虚结点,其值为一特定值,比如"#":

    void CreateBiTree(BiTree *T)

    {

        TElemType ch;

        

        /* scanf("%c",&ch); */

        ch=str[index++];

     

        if(ch=='#')

            *T=NULL;

        else    //如果ch!=#, 那么就生成一个新结点

        {

            *T=(BiTree)malloc(sizeof(BiTNode));    //sizeof(BiTNode)得到的是结构体的大小

            if(!*T)    //相当于if(T==0)

                exit(OVERFLOW);

            (*T)->data=ch; /* 生成根结点的数据 */

            CreateBiTree(&(*T)->lchild); /* 构造左子树 */

            CreateBiTree(&(*T)->rchild); /* 构造右子树 */

        }

    }

     

    /* 初始条件: 二叉树T存在 */

    /* 操作结果: T为空二叉树,则返回TRUE,否则FALSE */

    Status BiTreeEmpty(BiTree T)

    {

        if(T)

            return FALSE;

        else

            return TRUE;

    }

     

    #define ClearBiTree DestroyBiTree//意思是ClearBiTree()DestroyBiTree()意思是相同的

     

    /* 初始条件: 二叉树T存在。操作结果: 返回T的深度 */

    int BiTreeDepth(BiTree T)

    {

        int i,j;

        if(!T)

            return 0;

        if(T->lchild)

            i=BiTreeDepth(T->lchild);

        else

            i=0;

        if(T->rchild)

            j=BiTreeDepth(T->rchild);

        else

            j=0;

        return i>j?i+1:j+1;

    }

     

    /* 初始条件: 二叉树T存在。操作结果: 返回T的根 */

    TElemType Root(BiTree T)

    {

        if(BiTreeEmpty(T))

            return Nil;

        else

            return T->data;

    }

     

    /* 初始条件: 二叉树T存在,p指向T中某个结点 */

    /* 操作结果: 返回p所指结点的值 */

    TElemType Value(BiTree p)

    {

        return p->data;

    }

     

    /* p所指结点赋值为value */

    void Assign(BiTree p,TElemType value)

    {

        p->data=value;

    }

     

    /* 初始条件: 二叉树T存在 */

    /* 操作结果: 前序递归遍历T */

     

    /* 初始条件: 二叉树T存在 */

    /* 操作结果: 前序递归遍历T */

    void PreOrderTraverse(BiTree T)

    {

        if(T==NULL)

            return;

        printf("%c",T->data);/* 显示结点数据,可以更改为其它对结点操作 */

        PreOrderTraverse(T->lchild); /* 再先序遍历左子树 */

        PreOrderTraverse(T->rchild); /* 最后先序遍历右子树 */

    }

     

    /* 初始条件: 二叉树T存在 */

    /* 操作结果: 中序递归遍历T */

    void InOrderTraverse(BiTree T)

    {

        if(T==NULL)

            return;

        InOrderTraverse(T->lchild); /* 中序遍历左子树 */

        printf("%c",T->data);/* 显示结点数据,可以更改为其它对结点操作 */

        InOrderTraverse(T->rchild); /* 最后中序遍历右子树 */

    }

     

    /* 初始条件: 二叉树T存在 */

    /* 操作结果: 后序递归遍历T */

    void PostOrderTraverse(BiTree T)

    {

        if(T==NULL)

            return;

        PostOrderTraverse(T->lchild); /* 先后序遍历左子树 */

        PostOrderTraverse(T->rchild); /* 再后序遍历右子树 */

        printf("%c",T->data);/* 显示结点数据,可以更改为其它对结点操作 */

    }

     

    //主函数

    int main()

    {

        int i;

        BiTree T;

        TElemType e1;

        InitBiTree(&T);

     

        

        StrAssign(str,"ABDH#K###E##CFI###G#J##");

     

        CreateBiTree(&T);

     

        printf("构造空二叉树后,树空否?%d(1:0:) 树的深度=%d ",BiTreeEmpty(T),BiTreeDepth(T));

        e1=Root(T);

        printf("二叉树的根为: %c ",e1);

     

        printf(" 前序遍历二叉树:");

        PreOrderTraverse(T);

        printf(" 中序遍历二叉树:");

        InOrderTraverse(T);

        printf(" 后序遍历二叉树:");

        PostOrderTraverse(T);

        ClearBiTree(&T);

        printf(" 清除二叉树后,树空否?%d(1:0:) 树的深度=%d ",BiTreeEmpty(T),BiTreeDepth(T));

        i=Root(T);

        if(!i)

            printf("树空,无根 ");

        

        return 0;

    }

     

    ● 线索二叉树

    概念:

    指向前驱和后继的指针称为线索,加上线索的二叉链表称为线索链表,相应的二叉树就称为线索二叉树(Threaded Binary Tree)。

    线索化:对二叉树以某种次序(前, , )遍历使其变为线索二叉树的过程(即在遍历过程中用线索取代空指针)

     

    线索链表的结点:

    ltag为0, lchild指向该结点的左孩子,为1时指向该结点的前驱。

    rtag为0, rchild指向该结点的右孩子,为1时指向该结点的后继。

    从结点左边出发的线指向该节点的前驱, 从结点右边出发的线指向该节点的后继.

    左上图的遍历:

    前序遍历: abdgecf

    中序遍历: gdbeacf

    后序遍历: gdebfca

    前序遍历为: abdg###e##c#f##

    线索化的意义:

    从任一结点出发都能快速找到其前驱和后继,且不必借助堆栈。

    如果二叉树需要经常遍历, 则采用线索二叉树较好(因为借助线索二叉树遍历二叉树, 无需堆栈/递归算法)

     

    //起始部分

    #include "string.h"

    #include "stdio.h"

    #include "stdlib.h"

    #include "io.h"

    #include "math.h"

    #include "time.h"

     

    #define OK 1

    #define ERROR 0

    #define TRUE 1

    #define FALSE 0

     

    #define MAXSIZE 100 /* 存储空间初始分配量 */

     

    typedef int Status;    /* Status是函数的类型,其值是函数结果状态代码,如OK等 */

    typedef char TElemType;

    typedef enum {Link,Thread} PointerTag;    /* Link==0表示指向左右孩子指针, */

                                            /* Thread==1表示指向前驱或后继的线索 */

     

    //二叉线索存储结点结构

    typedef struct BiThrNode    /* 二叉线索存储结点结构 */

    {

        TElemType data;    /* 结点数据 */

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

        PointerTag LTag;

        PointerTag RTag;        /* 左右标志 */

    } BiThrNode, *BiThrTree;

     

    TElemType Nil='#'; /* 字符型以#符为空 */

     

    Status visit(TElemType e)

    {

        printf("%c ",e);

        return OK;

    }

     

    /* 按前序输入二叉线索树中结点的值,构造二叉线索树T */

    /* 0(整型)/空格(字符型)表示空结点 */

    Status CreateBiThrTree(BiThrTree *T)

    {

        TElemType h;

        scanf("%c",&h);

     

        if(h==Nil)

            *T=NULL;

        else

        {

            *T=(BiThrTree)malloc(sizeof(BiThrNode));

            if(!*T)

                exit(OVERFLOW);

            (*T)->data=h; /* 生成根结点(前序) */

            CreateBiThrTree(&(*T)->lchild); /* 递归构造左子树 */

            if((*T)->lchild) /* 有左孩子 */

                (*T)->LTag=Link;

            CreateBiThrTree(&(*T)->rchild); /* 递归构造右子树 */

            if((*T)->rchild) /* 有右孩子 */

                (*T)->RTag=Link;

        }

        return OK;

    }

     

    //中序遍历进行中序线索化

    BiThrTree pre; /* 全局变量,始终指向刚刚访问过的结点 */

    /* 中序遍历进行中序线索化 */

    void InThreading(BiThrTree p)

    {

        if(p)

        {

            InThreading(p->lchild); /* 递归左子树线索化 */

            if(!p->lchild) /* 如果某结点的左指针域为空 */

            {

                p->LTag=Thread; /* 前驱线索 */

                p->lchild=pre; /* 左孩子指针指向前驱 */

            }

            if(!pre->rchild) /* 前驱没有右孩子 */

            {

                pre->RTag=Thread; /* 后继线索 */

                pre->rchild=p; /* 前驱右孩子指针指向后继(当前结点p) */

            }

            pre=p; /* 保持pre指向p的前驱 */

            InThreading(p->rchild); /* 递归右子树线索化 */

        }

    }

     

    /* 中序遍历二叉树T,并将其中序线索化,Thrt指向头结点 */

    Status InOrderThreading(BiThrTree *Thrt,BiThrTree T)

    {

        *Thrt=(BiThrTree)malloc(sizeof(BiThrNode));

        if(!*Thrt)

            exit(OVERFLOW);

        (*Thrt)->LTag=Link; /* 建头结点 */

        (*Thrt)->RTag=Thread;

        (*Thrt)->rchild=(*Thrt); /* 右指针回指 */

        if(!T) /* 若二叉树空,则左指针回指 */

            (*Thrt)->lchild=*Thrt;

        else

        {

            (*Thrt)->lchild=T;

            pre=(*Thrt);

            InThreading(T); /* 中序遍历进行中序线索化 */

            pre->rchild=*Thrt;

            pre->RTag=Thread; /* 最后一个结点线索化 */

            (*Thrt)->rchild=pre;

        }

        return OK;

    }

     

    /* 中序遍历二叉线索树T(头结点)的非递归算法 */

    Status InOrderTraverse_Thr(BiThrTree T)

    {

        BiThrTree p;

        p=T->lchild; /* p指向根结点 */

        while(p!=T)

        { /* 空树或遍历结束时,p==T */

            while(p->LTag==Link)

                p=p->lchild;

            if(!visit(p->data)) /* 访问其左子树为空的结点 */

                return ERROR;

            while(p->RTag==Thread&&p->rchild!=T)

            {

                p=p->rchild;

                visit(p->data); /* 访问后继结点 */

            }

            p=p->rchild;

        }

        return OK;

    }

     

    //主函数

    int main()

    {

        BiThrTree H,T;

        printf("请按前序输入二叉树(:'ABDH##I##EJ###CF##G##') ");

         CreateBiThrTree(&T); /* 按前序产生二叉树 */

        InOrderThreading(&H,T); /* 中序遍历,并中序线索化二叉树 */

        printf("中序遍历(输出)二叉线索树: ");

        InOrderTraverse_Thr(H); /* 中序遍历(输出)二叉线索树 */

        printf(" ");

        

        return 0;

    }

    //上面的程序:

    1. 通过前序遍历扩展二叉树的方法建造一个二叉树; ② 将二叉树中序线索化; ③ 利用得到的线索二叉树中序遍历该书.

     

    //最优二叉树/Huffman:

    带权路径长度WPL最小的二叉树

    //构造Huffman树的基本思想:

    权值大的结点用短路径,权值小的结点用长路径。

    //构造Huffman树的步骤(即Huffman算法):

    (1) 由给定的 n 个权值{ w1, w2, …, wn }构成n棵二叉树的集合F = { T1, T2, …, Tn } (即森林) ,其中每棵二叉树 Ti 中只有一个带权为 wi 的根结点,其左右子树均空。

    (2) F 中选取两棵根结点权值最小的树 做为左右子树构造一棵新的二叉树,且让新二叉树根结点的权值等于其左右子树的根结点权值之和。

    (3) F 中删去这两棵树,同时将新得到的二叉树加入 F中。

    (4) 重复(2) (3) , 直到 F 只含一棵树为止。这棵树便是Huffman树。

     

    ● 赫夫曼编码

    赫夫曼编码属于贪心算法.

    贪心算法(又称 贪婪算法)是指,在对问题求解时总是做出在当前看来是最好的选择。不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部 最优解。

     

    文本文件的编码方式:

    ① 等长编码

    假设一个文本文件面存有abcdef六个字符

    方案1: 采用ASCII, 那么对应的二进制应该为:

    01100001·01100010·01100011·01100100·01100101·01100110;

    方案2: 创建一种新的编码, :

    000·001·010·011·100·101;

    这样,相比ASCII码压缩了近乎3/8, 但是如果我们用普通编辑器打开文件的话,会发现全是乱码,解决方法是a. 开发一个对应编码的编辑器了; b. 保存的时候我们使用新的较短的编码,在读取的时候,再转换成ASCII.

     

    ② 变长编码

    在现实中,各个字符在文件里面占有的比例一般是不同的, 例如现在存在一个有100个字符的ASCII码文件,其出现次数分别为:

    使用等长编码占用的位数为:(45+13+12+16+9+5)3=300

     

    我们采用一种变长编码的形式来重新压缩这个文件,将占有比重大的字符编码变短,将占有比重小的文件编码拉长:

    占用的位数为:451+133+123+163+94+54=224,相比于上面的等长编码,大约节省了25%的空间.

     

    ③ 前缀码

    前缀码设计原则: 没有任何码字是其他码字的前缀

    由哈夫曼树求得的编码为最优前缀码.

     

    假设六个字母的频率为A 27B 8C 15D15E 30F 5,合起来是100%, 我们完全可以重新按照赫夫曼树来规划它们。

    左图为构造赫夫曼树的过程的权值显示。右图为将权值左分支改为0,右分支改为1后的赫夫曼树。

    我们对这六个字母用其从树根到叶子所经过路径的01来编码,可以得到如下编码:

    将文字内容为"BADCADFEED"再次编码,对比可以看到结果串变小了:

    等长编码二进制串:001000011010000011101100100011(共30个字符)

    赫夫曼编码二进制串:1001010010101001000111100(共25个字符)

     

    • ①发送方和接收方必须要约定好同样的赫夫曼编码规则.

      ②赫夫曼树并不唯一, 但带权路径长度一定是相同的.

     

    //赫夫曼编码的实现

    #include <stdio.h>

    #include <limits.h>

    #include <stdlib.h>

    #include <string.h>

     

    typedef struct

    {

        unsigned int weight;

        unsigned int parent,lchild,rchild;

    }HTNode,*HuffmanTree; // 动态分配数组存储赫夫曼树

    typedef char **HuffmanCode; // 动态分配数组存储赫夫曼编码表

     

    // 返回i个结点中权值最小的树的根结点序号,该函数将由下面的select()函数调用

    int min(HuffmanTree t,int i)

    {

        int j,flag;

        unsigned int k=UINT_MAX; // k为不小于可能的值(无符号整型最大值)

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

            if(t[j].weight<k&&t[j].parent==0) // t[j] 是树的根结点

                k=t[j].weight,flag=j;

            t[flag].parent=1; // 给选中的根结点的双亲赋1,避免第2次查找该结点

            return flag;

    }

     

    // i个结点中选择2个权值最小的树的根结点序号, s1为其中序号小的那个

    void select(HuffmanTree t,int i,int &s1,int &s2)

    {

        int j;

        s1=min(t,i);

        s2=min(t,i);

        if(s1>s2)

        {

            j=s1;

            s1=s2;

            s2=j;

        }

    }

     

    // w存放n个字符的权值(>0) ,构造赫夫曼树HT,并求出n个字符的赫夫曼编码HC

    void HuffmanCoding(HuffmanTree &HT,HuffmanCode &HC,int *w,int n) // 算法 6.12

    {

        int m,i,s1,s2,start;

        unsigned c,f;

        HuffmanTree p;

        char *cd;

        if(n<=1)

            return;

        m=2*n-1;

        HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode)); // 0号单元未用

        for(p=HT+1,i=1;i<=n;++i,++p,++w)

        {

            (*p).weight=*w;

            (*p).parent=0;

            (*p).lchild=0;

            (*p).rchild=0;

        }

        for(;i<=m;++i,++p)

            (*p).parent=0;

        for(i=n+1;i<=m;++i) // 建赫夫曼树

        { // 在 HT[1~ i-1] 中选择 parent为 0且 weight最小的两个结点,其序号分别为 s1和 s2

            select(HT,i-1,s1,s2);

            HT[s1].parent=HT[s2].parent=i;

            HT[i].lchild=s1;

            HT[i].rchild=s2;

            HT[i].weight=HT[s1].weight+HT[s2].weight;

        }

        // 从叶子到根逆向求每个字符的赫夫曼编码

        HC=(HuffmanCode)malloc((n+1)*sizeof(char*));

        // 分配 n个字符编码的头指针向量 ([0] 不用 )

        cd=(char*)malloc(n*sizeof(char)); // 分配求编码的工作空间

        cd[n-1]=''; // 编码结束符

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

        { // 逐个字符求赫夫曼编码

            start=n-1; // 编码结束符位置

            for(c=i,f=HT[i].parent;f!=0;c=f,f=HT[f].parent)

                // 从叶子到根逆向求编码

                if(HT[f].lchild==c)

                    cd[--start]='0';

                else

                    cd[--start]='1';

                HC[i]=(char*)malloc((n-start)*sizeof(char));

                // 为第 i个字符编码分配空间

                strcpy(HC[i],&cd[start]); // cd复制编码 () HC

        }

        free(cd); // 释放工作空间

    }

     

    //主函数

    void main()

    {

        HuffmanTree HT;

        HuffmanCode HC;

        int *w,n,i;

        printf("请输入权值的个数 (>1): ");

        scanf("%d",&n);

        w=(int*)malloc(n*sizeof(int));

        printf("请依次输入 %d个权值 (整型 ): ",n);

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

            scanf("%d",w+i);

        HuffmanCoding(HT,HC,w,n);

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

            puts(HC[i]);

    }

     

     

  • 相关阅读:
    上网助手(集成ipv6)windows版
    c# 串口编程
    test blog
    用于主题检测的临时日志(d020b283408c4bc68872f97ee237b663 3bfe001a32de4114a6b44005b770f6d7)
    OpenGL概述 陌陌
    [转载][转帖]谈谈我对攻读计算机研究生的看法。。。大牛的文章,见解精深独到
    滚动值的兼容问题
    js小练习去掉指定的字符组成一句话输出
    马虎将classname加到了id属性中,造成报错
    锋利的jquery读书笔记(一)
  • 原文地址:https://www.cnblogs.com/ArrozZhu/p/8388408.html
Copyright © 2011-2022 走看看