其他分享
首页 > 其他分享> > 数据结构——树

数据结构——树

作者:互联网

文章目录

1. 树

2. 二叉树

2.2 二叉树的性质

  1. 二叉树的第i层上有2^(i-1)个结点
  2. 深度为k的二叉树,最多有(2^k)-1个结点;最少有K个结点(每层只有一个)
  3. 任何一棵二叉树T,如果叶子树为n0,度为2的结点数为n2,则n0=n2+1
  4. 具有n个节点的完全二叉树深度为[log_2n]+1
  5. 如果对一棵有n个节点的完全二叉树的节点按程序编号,则对任意节点i有:
    1. 如果i=1则节点i是二叉树的根无双亲;如果a>1则其双亲是节点[i/2]
      2.如果2i>n,则节点a为子,节点无左孩子,否则其左孩子是节点时孩子2i
    2. 如果2i+1>n,则节点无右孩子,否则其右孩子是结点2i+1。

满二叉树

每一层上的结点都是满的。

完全二叉树

在满二叉树中,从最后一个节点开始连续去掉任意一个相连的节点,既是一颗完全二叉树。

2.3二叉树的遍历

二叉树的遍历有四种:前序遍历,中序遍历,后序遍历,层序遍历

前序遍历

中序遍历

后序遍历

二叉树的建立

struct CreateRiTree(BiTree &T)
{
    cin >> ch ;
    if(ch=="#")
    {
        T=NULL;
    }
    else
    {
        if(!(T=(BiTNode*)malloc(sizeof(BiTNode))))
        {
            T = new BiTNode ;
        }
        T->data = ch;  //生成根节点
        CreateRiTree(T->LChild);  //创建左子树
        CreateRiTree(T->RChild);  //创建右子树
    }
    return OK;                     
}

递归遍历算法

先序遍历算法

struct PerOrderTraverse(BiTree T)
{
    if(T = NULL)
    {
        return ok;
    }
    else
    {
        cout << T->data;  //先输出根结点
        PerOrderTraverse(T->LChild);
        PerOrderTraverse(T->RChild);
    }
}

中序遍历算法

struct PerOrderTraverse(BiTree T)
{
    if(T = NULL)
    {
        return ok;
    }
    else
    {
        PerOrderTraverse(T->LChild);
         cout << T->data; 
        PerOrderTraverse(T->RChild);
    }
}

后序遍历算法

struct PerOrderTraverse(BiTree T)
{
    if(T = NULL)
    {
        return ok;
    }
    else
    {
        PerOrderTraverse(T->LChild);
        PerOrderTraverse(T->RChild);
         cout << T->data;  
    }
}

遍历算法的分析

时间效率:O(n) 每个结点都要访问一次
空间效率:O(n) 栈占用的最大辅助空间

非递归遍历算法

复制二叉树

int Copy(BiTree T, BiTree &NewT)
{
    //如果树为空,递归结束
    if(T==NULL)
    {
        NewT = NULL;
        retrun 0;
    }
    //否则申请新空间,复制根结点
    else
    {
        // 申请新空间给要被复制的新量
        NewT = new BiTNode;
        // 把原数据赋值给新量
        NewT->data = T->data;
        // 同样递归的方法,把原来左子树分的值赋值给新的左子树
        Copy(T->LChild, NewT->LChild);
        Copy(T->RChild, NewT->RChild);

    }
}

计算二叉树的深度

  1. 如果是空树,深度为0;
  2. 否则,递归计算左子树深度记作m,递归计算右子树深度记作n
  3. 深度为取n,m中较大的那个数+1,
int Depth(BiTree T)
{
    // 判断树是否为空
    if(T==NULL)
    {
        return 0;
    }
    // 不为空的情况下,同样求左右子树的深度
    else 
    {
        m= Depth(T->LChild);
        n= Depth(T->RChild);
        // +1 是因为深度要在左右子树的基础上在加上根结点
        return (n>=m?n:m)+1;
    }
}

计算二叉树的结点数

  1. 判断如果空树结点数为0
  2. 结点数=左节点数+右节点数+1
int NodeCount(BiTree T)
{
    // 如果空树结点数为0
    if(T==NULL)
    {
        return 0;
    }
    else
    {
        // 结点数=左节点数+右节点数+1
        return NodeCount(T->LChild)+NodeCount(T->RChild)+1;
    }
}

计算二叉树的叶子结点数

  1. 判断如果空树结点数为0
  2. 叶子结点数=左节点叶子结点数+右节点叶子结点数
int LeafCount(BiTree T)
{
     // 如果空树叶子结点数为0
    if(T==NULL)
    {
        return 0;
    }
    // 只要一个根结点,叶子数为1
    if(T->LChild==NULL&&T->RChild==NULL)
    {
        return 1;
    }
    else
    {
        return LeafCount(T->LChild)+LeafCount(T->RChild); 
    }
}

2.4 线索二叉树

二叉树在寻找她的左右孩子很方便,但是无法直接寻找该节点在某种序列中的前驱结点和后继节点

3. 森林

是N(N>=0)棵互不相交的树的集合

3.1 树的存储结构

双亲表示法

数组下标数据双亲下标
0R-1
1A0
2B0
3C0
4D1
5E1
6F3
7G6
8H6
9H6
typedef struct PTNode
{
    TElemType data;
    int parent  // 记录双亲节点的位置
}

孩子链表表示法

把每个节点的孩子孩子排列起来,看成是一个线性表,用单链表存起来,N个结点有N个孩子链表。每个孩子链表并不存放具体数据,而是存放该孩子结点的地址,也为孩子结点数据已经存储了,而且该孩子结点可能有自己的孩子结点,在存储她的数据就会造成浪费

孩子结点的结构:child next

typedef struct CTNode
{
    int child;
    struct CTNode *next;
}*childPtr;

双亲结点的结构 data firstchild

typedef Struct 
{
    TELemType data;
    ChildPtr firstchild; // 孩子链表的标头指针
}CTBox;

孩子兄弟表示法

有二叉树的形式来存储,链表中每个结点有两个指针,左边指针指向第一个孩子结点,右边指针指向下一个兄弟结点

typedef struct CSNode
{
    ElemType data;
    struct CSNode *firstChild *nextSibling;
}CSNode,*CSTree;

把树树转成二叉树

树的每个结点可能有多个子树,要想转化为二叉树,需要转化,把一个结点的左节点指向她的第一个子节点,右节点指向他的下一个兄弟结点

森林转化为二叉树

  1. 将森林中的每棵树转化成二叉树
  2. 将每棵树的根节点用线连起来
  3. 第一棵树的很结点作为整个二叉树的根,再以根节点为轴心,顺时针旋转,构成二叉树型结构

二叉树转换回森林

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

3.2 树的遍历

4. 哈斯曼树

哈斯曼树是用来构造最优二叉树的

哈斯曼树:带权路径长度最短的树
是在度相同的树中比较而得的结果,因此有最优二叉树、最优三叉树之称

4.1 哈斯曼树的构造

  1. 把所有数据都设置成根结点
  2. 在在所根结点中,选出两个最小的结点a、b,构成一个二叉树,a和b的根结点为新生成的c,c的权=a权+b权,同时在原来所有根结点中删除a、b。
  3. 在从现有根结点中在挑选出两个最小的生成二叉树,这里包含上一步a、b生成的新结点c
  4. 不断重复两个小树生成一个新树,直到构成一个二叉树位置

哈斯曼树的算法实现

// 结点类型定义
typedef struct 
{
    int weight;   //结点的权值
    int parent,Lch,Rch;   
}HTNode,*HuffmanTree;

// 初始化哈斯曼树
void CreatHuffmanTree(HuffManTree HT, int n)
{
    if(n<1) // 结点数为0,退出
    {
        return ;
    }
    int m=2*n-1;  // n个结点组成二叉树结点共2n-1个元素
    HT = new HtNode[m+1]  // 0号单元不使用,HT[m]就便是根结点。
    for(int i=1;i<=m;++i)  // 将2n-1个元素的parent,Lch,Rch都设置为0
    {
        HT[i].Lch=0;
        HT[i].Rch=0;
        HT[i].parent=0;
    }
    for(int i=1;i<n;++i) // 输入前n个元素的weight值
    {
        cin >> HT[i].weight;
    } 
}

// 构造哈斯曼树
for(i=n+1;i<=m;i++)  // 合并产生n-1个新结点——构造哈斯曼树(1-n为原有结点,n+1-2n-1为新生成的结点)
{
    Select(HT,i-1,s1,s2);   // 在原有1-n个结点中选择处2个双亲为0,
                            // 并且权重最小的结点
                            // 并返回他们在HT表中的序号s1,s2
    HT[s1].parent=i;  // 表示从表中删除s1,s2
    HT[s2].parent=i;
    HT[i].Lch=s1;   // s1,s2分别作为i的左右孩子
    HT[i].Rch=s2;
    HT[i].weight=HT[s1].weight+HT[s2].weight;   // i的权值为左右孩子的权值之和
}

哈斯曼编码

为每个字符设置二进制编码的时候,每一位都要有相同的位数,为一些出现次数多的数据设置段的编码,可以节省存储空间,但是设计编码的时候,这些特殊的编码,不能是其它正常编码的前缀。

设计前缀码,同时让电文总长最短——哈斯曼编码

  1. 统计字符集中每个字符出现的概率。
  2. 利用哈斯曼树的特点,字符的概率值为其权值,构造哈斯曼树,权值越大,路径越短
  3. 哈斯曼树每个分支:左分支记为0,右分支记为1
  4. 每个分支都有1或0,每个结点的编号就是从根结点到这个结点的所有分支上的数连起来,从根结点的数开始大头

哈斯曼编码的代码实现

// 从子叶向根逆向求每个字符的哈斯曼编码,存储在编码表HC中
void CreatHuffmanCode(HuffmanTreee HT,HuffmanCode &HC ,int n)
{
    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) // 结点C是f的左孩子,则生成代码0
            {
                cd[start]='0';
            }
            else                // 结点C是f的右孩子,则生成代码1
            {
                cd[start]='1';
            }
            c = f;          // 继续向上回溯
            f = HT[f].parent;
        }                   // 求出第i个字符编码
        HC[i] = new char[n-start]; // 为第i个字符串编码分配空间 
        strcpy(HC[i],&cd[start]);  // 讲求的的编码从临时空间cd复制到HC的当前行中

        delete cd;          // 释放临时空间
    }
}

标签:结点,遍历,HT,哈斯,数据结构,节点,二叉树
来源: https://blog.csdn.net/mankeywang/article/details/121193655