数据结构——复习六(树)

树描述层次结构,是一种一对多的逻辑关系
树的定义
树是由n个结点组成的有限集合。
如果n=0,称为空树
如果n>0,则:
有一个特定的称之为根(root)的结点,它只有后继,但没有前驱;
除根以外的其它结点划分为m (m≥0)个互不相交的有限集合T0, T1, …, Tm-1,每个集合本身又是一棵树,并且称之为根的子树(subTree)。每棵子树的根结点有且仅有一个直接前驱,但可以有0个或多个后继。
树的表示方式
(a)树形表示
(b)凹入表
(c)文氏图
基本术语
度:
一个结点的子树个数称为该结点的度。一棵树的度是指该树中结点的最大度数。
叶子或终端结点:
度为0的结点称为叶子。度不为0的结点称为分支结点或非终端结点,除根结点之外的分支结点统称为内部结点。
孩子和双亲:
结点的子树的根称为该结点的孩子,该结点称为孩子的双亲或父亲。
兄弟:
同一个双亲的孩子彼此称作兄弟。
祖先和子孙:
结点的祖先是从根到该结点所经分支上的所有结点。反之,以某结点为根的子树中的任一结点都称为该结点的子孙。
层数:
从根结点开始定义,根为第一层,其余结点的层数等于其双亲结点的层数加1。其双亲在同一层上的结点互为堂兄弟。
树的深度或高度:
树结点中的最大层数称为树的高度或深度。
无序树、有序树:
如果将树中结点的各子树看成从左到右是有次序的(即不能互换),则称该树为有序树。否则称为无序树。
结点的次序:
在有序树中可以从左到右地规定结点的次序。按从左到右的顺序,我们可以把一个结点的最左边的子结点简称为最左子结点,或直接称为长子,而把长子右边的结点称为次子。
森林:
是m(m>=0)棵互不相交的树的集合。删除一棵树的根就得到一个森林。
二叉树
(1)每个结点至多有二棵子树(即不存在度大于2的结点)
(2)二叉树的子树有左、右之分,且其次序不能任意颠倒,只有一棵子树时也必须分清左右子树。
性质
性质1:一个非空二叉树的第i层上至多有2^(i-1)个结点(i≥0)
性质2:深度为k的二叉树至多有2^k-1个结点(k ≥1)
性质3:对任何一棵非空二叉树T,如果叶结点数为n0,度为2的结点数为n2,则n0=n2+1。
n0+n1+n2 = 所有结点的度数和+1 = n1+ 2*n2 +1
扩展一下可以得到:
数据结构——复习六(树)_第1张图片
满二叉树
度为k且有2^k-1个结点的二叉树,不存在度为1的结点。
编号方式为:从上到下,从左到右。
完全二叉树
深度为k,有n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树编号从1到n的结点一一对应时,称为完全二叉树。
特点:
(1)叶子结点只可能在层次最大的两层上出现。
(2)任一结点,若其左分支下的子孙的最大层次为h,则其右分支下的最大层次为h或h-1,即若结点无左子女,决不应有右子女。
性质4:具有n个结点的完全二叉树的深度k为log2n(取下界) +1
性质5:如果对一棵有n个结点的完全二叉树按层次次序从1开始编号,则对任一结点i(1 ≤i≤n),有:
1)i=1,序号结点i是根;
i>1, 其双亲结点是i/2(取下界)。
2)2i≤n,序号为i的结点的左子女结点的序号为2i;
2i>n,序号为i的结点无左子女。
3)2i+1≤ n,序号为i的结点的右子女结点的序号为2i+1;
2i+1>n,序号为i的结点无右子女。
完全二叉树的推论:
n个结点的完全二叉树中:
度为1的结点数为(n+1)%2;
度为0的结点数为(n+1)/2(取下界);
度为2的结点数为(n+1)/2(取下界)-1;
编号最大的分支结点是n/2(取下界);
编号最小的叶子结点是n/2(取下界)+1。
n个结点的二叉树:
高最多为n;
最低为log2n(取下界)+1 (完全二叉树)。
二叉树的遍历
先序遍历
先序遍历二叉树算法的框架是:
若二叉树为空,则空操作;
否则
访问根结点 (D);
先序遍历左子树 (L);
先序遍历右子树 ®。
先序遍历的递归算法

void PreOrderTraverse(BiTree T)
{
	if(T)
	{
		printf("%c",T->data);
		PreOrderTraverse(T->lchild);
		PreOrderTraverse(T->rchild);
	}
}

数据结构——复习六(树)_第2张图片
遍历结果: - + a * b - c d / e f
中序遍历
中序遍历二叉树算法的框架是:
若二叉树为空,则空操作;
否则
中序遍历左子树 (L);
访问根结点 (D);
中序遍历右子树 ®。

void InOrderTraverse(BiTree T)
{
	if(T)
	{
		InOrderTraverse(T->lchild);
		printf("%c",T->data);
		InOrderTraverse(T->rchild);
	}
}

数据结构——复习六(树)_第3张图片
遍历结果:a + b * c - d - e / f
后序遍历
后序遍历二叉树算法的框架是
若二叉树为空,则空操作;
否则
后序遍历左子树 (L);
后序遍历右子树 ®;
访问根结点 (D)。

void PostOrderTraverse(BiTree T)
{
	if(T)
	{
		PostOrderTraverse(T->lchild);
		PostOrderTraverse(T->rchild);
		printf("%c",T->data);
	}
}

数据结构——复习六(树)_第4张图片
遍历结果: a b c d - * + e f / -
//非递归以后再补吧
构造二叉树
读入一棵二叉树对应的扩充二叉树的前序遍历的结点值序列。每读入一个结点值就进行分析:
◆ 若是扩充结点值:令根指针为NULL;
◆ 若是(正常)结点值:动态地为根指针分配一个结点,将该值赋给根结点,然后递归地创建根的左子树和右子树。

int CreateBiTree(BiTree &T)
{   
    char ch;
    scanf("%c",&ch);
    if(ch=='@') 
    T=NULL;
    else
    { 
       T=(BiTree)malloc(sizeof(BiTNode));
       T->data=ch;
       CreateBiTree(T->lChild);
       CreateBiTree(T->rChild);
    }
}

二叉树深度

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

二叉树结点个数

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

二叉树叶子结点个数

int LeafCount(BiTree T)
{
    if(!T) 
    return 0;                     //空树没有叶子
    else 
    if(T->lchild==NULL&&T->rchild==NULL) 
    return 1; //叶子结点
    else  
    return LeafCount(T->lchild)+LeafCount(T->rchild);
    //左子树叶子数加上右子树叶子数
}

层次遍历二叉树
//这个不能直接运行起来,要自己构建队列,也可以直接用库里的

typedef BiTNode* ElemType;
typedef struct
{
    ElemType base[M];
    int front,rear;
} SqQueue;
void LevelOrderTraverse(BiTree T)
{
    BiTree p;
    SqQueue Q;
    Q.rear=Q.front=0;
    if(T)
    {
        Q.base[Q.rear]=T;
        Q.rear=(Q.rear+1)%MAXQSIZE;
        while(Q.front!=Q.rear)
        {
            p=Q.base[Q.front];
            printf("%c",p->data);
            Q.front=(Q.front+1)%MAXQSIZE;
            if (p->lchild)
            {
                Q.base[Q.rear]=p->lchild;
                Q.rear=(Q.rear+1)%MAXQSIZE;
            }
            if (p->rchild)
            {
                Q.base[Q.rear]=p->rchild;
                Q.rear=(Q.rear+1)%MAXQSIZE;
            }
        }
    }
}

线索二叉树
二叉树遍历后,结点的前驱和后继信息能够反映出来
考察二叉链表表示中的空指针域:n+1个域是空,可以利用这些来体现
//后序和先序也能很容易画出来
数据结构——复习六(树)_第5张图片
线索二叉树的二叉链表表示(中序序列)
D B A E G C H F I
可以加头结点,但我没看出有啥用
数据结构——复习六(树)_第6张图片
相关概念
前驱与后继:在二叉树的先序、中序或后序遍历序列中两个相邻的结点互称为~
线索:指向前驱或后继结点的指针。
线索二叉树:加上线索的二叉链表表示的二叉树。
线索化:将二叉树变为线索二叉树的过程称为线索化。
按中序线索化二叉树:按中序遍历二叉树的顺序对二叉树进行线索化。
按中序遍历中序线索树:沿线索遍历。
线索二叉树的实现
在有n个结点的二叉链表中必定有n+1个空链域
在线索二叉树的结点中增加两个标志域
ltag:若 ltag =0, lchild 域指向左孩子;若 ltag=1, lchild域指向其前驱
rtag:若 rtag =0, rchild 域指向右孩子;若 rtag=1, rchild域指向其后继

typedef  struct BiThrNode
{
    ElemType data;
    struct  BiThrNode *lchild,*rchild;
    int ltag,rtag;
}BiThrNode,*BiThrTree;
BiThrTree T;

中序线索化二叉树

BithrTree pre=NULL;
void InorderThread(BiThrTree  p)
{
    if(p!=NULL)
    {
        InorderThread(p->lchild);
        if(p->lchild==NULL) 
        p->ltag=1;
        if(p->rchild==NULL) 
        p->rtag=1;
        if(pre!=NULL)
        {
            if(pre->rtag==1) 
            pre->rchild=p;
            if(p->ltag==1) 
            p->lchild=pre;
        }
        pre=p;
        InorderThread(p->rchild);
    }
}

中序遍历中序线索二叉树
在中序线索二叉树中找结点后继的方法:
(1)若rtag=1, 则rchild域直接指向其后继
(2)若rtag=0, 则结点的后继应是其右子树的左链尾(ltag=1)的结点
在中序线索二叉树中找结点前驱的方法:
(1)若ltag=1, 则lchild域直接指向其前驱
(2)若ltag=0, 则结点的前驱应是其左子树的右链尾(rtag=1)的结点

void InorderTraverseThr(BiThrTree p)
{
    while(p)
    {
        while(p->ltag==0) 
        p=p->lchild;
        visited(p->data);
        while(p->rtag==1)
        {
            p=p->rchild;
            visited(p->data);
        }
        p=p->rchild;
    }
}

先序遍历线索二叉树
数据结构——复习六(树)_第7张图片
后序遍历线索二叉树
数据结构——复习六(树)_第8张图片
树和森林
森林:是m(m>=0)棵互不相交的树所组成的集合。
树的存储结构
1 双亲表示法

typedef struct  Ptnode{
    ElemType info;
    int	parent;    
}Ptnode;
typedef  struct{
    Ptnode nodes[MAX_NODE];
    int n;
}Ptree;

数据结构——复习六(树)_第9张图片
2 孩子表示法

typedef struct Ctnode{
    int child;
    struct Ctnode *next;
}*childlink;
typedef struct{
    ElemType info;
    childlink children;
}CTBox;
CTBox nodes[MAX_NODE];

数据结构——复习六(树)_第10张图片
3 孩子兄弟表示法

typedef struct CSNode{
    ElemType info;
    struct CSNode *firstchild,*nextsibling;
}CSNode,*CSTree;

数据结构——复习六(树)_第11张图片
树,森林与二叉树的转换
将树转换成二叉树
步骤:
连线:在兄弟之间加一连线
切线:对每个结点,除了其左孩子外,去除其与其余孩子之间的关系
旋转:以树的根结点为轴心,将整树顺时针转45°
数据结构——复习六(树)_第12张图片
将二叉树转换成树
步骤:
加线:若p结点是双亲结点的左孩子,则将p的右孩子,右孩子的右孩子,……沿分支找到的所有右孩子,都与p的双亲用线连起来
抹线:抹掉原二叉树中双亲与右孩子之间的连线
调整:将结点按层次排列,形成树结构
数据结构——复习六(树)_第13张图片
森林转换成二叉树
步骤:
将各棵树分别转换成二叉树
将每棵树的根结点用线相连
以第一棵树根结点为二叉树的根,再以根结点为轴心,顺时针旋转,构成二叉树型结构
数据结构——复习六(树)_第14张图片
二叉树转换成森林
步骤:
抹线:将二叉树中根结点与其右孩子的连线,及沿右分支搜索到的所有右孩子间连线全部抹掉,使之变成孤立的二叉树
还原:将孤立的二叉树还原成树
数据结构——复习六(树)_第15张图片
树和森林的遍历
树的遍历(先根遍历、后根遍历、层次遍历)
先根遍历:先根访问树的根结点,然后依次先根遍历根的每棵子树
后根遍历:先依次后根遍历每棵子树,然后访问根结点
树的先根遍历 转换后的二叉树的先序遍历
树的后根遍历 转换后的二叉树的中序遍历
森林的遍历
(1)先根遍历
若森林F为空, 返回;否则
访问F的第一棵树的根结点
先根次序遍历第一棵树的子树森林
先根次序遍历其它树组成的森林
(2)后根遍历
若森林F为空,返回;否则
后根次序遍历第一棵树的子树森林
访问F的第一棵树的根结点
后根次序遍历其它树组成的森林
哈夫曼树

typedef struct{
    int weight;
    int parent,lchild,rchild;
}HufmTree;
HufmTree tree[2*n-1];
void huffman(HufmTree tree[],int n)
{
    int i,j,x1,x2,n1,n2;
    if(n<=1) return;
    for(i= 0;i<2*n-1;i++)
    {
        /* 置初态 */
        tree[i].lchild=-1;
        tree[i].rchild=-1;
        tree[i].parent=-1;
        if(i<n)
            tree[i].weight=w[i];
        else
            tree[i].weight=-1;
    }
    for(i=0;i<n-1;i++)
    {
        n1=MAXINT;
        n2=MAXINT;
        x1=-1;
        x2=-1;
        for(j=0;j<n+i;j++)	/* 找两个最小权的无父结点的结点 */
            if(tree[j].weight<n1&&tree[j].parent==-1)
            {
                n2=n1;
                x2=x1;
                n1=tree[j].weight;
                x1=j;
            }
            else 
            if(tree[j].weight<n2&&tree[j].parent==-1)
            {
                n2=tree[j].weight;
                x2=j;
            }
        tree[x1].parent=n+i;
        tree[x2].parent=n+i;
        tree[n+i].weight=n1+n2;
        tree[n+i].lchild=x1;
        tree[n+i].rchild=x2;
    }
}

哈夫曼编码
数据结构——复习六(树)_第16张图片

你可能感兴趣的:(复习用的)