《数据结构》_5数和二叉树

树的定义

基本术语

  结点、路径(注意方向,由根往子节点)、孩子和双亲、兄弟、祖先、后裔、结点的度(某个结点拥有的子树数量)、叶子、分支结点、树的度、层次、高度、有序树和无序树、森林

树的抽象数据类型

树的存储表示

  • 双亲表示法
  • 孩子表示法
  • 孩子兄弟表示法

二叉树

二叉树的定义及主要性质

二叉树是n个结点的有限集合,该集合或者为空,或者是由根节点和互不相交的左子树和右子树组成,左子树和右子树均为二叉树。

特殊的二叉树

  • 满二叉树:在一棵二叉树中,所有的叶子节点都在同一层上,除叶子结点外,所有的分支结点都有两棵子树。
  • 完全二叉树:从上到下、从左到右依次编号的时候,二叉树每一个节点的编号和同深度满二叉树的结点编号从0至n-1所在位置一一对应,则此时为完全二叉树。在完全二叉树中,只有最下面两层结点的度可以小于2,最下面一层的叶子节点编号连续集中在靠左的位置上。
    •   【例】设完全二叉树顺序存储在一维数组中,则一个下标为k的非根节点的双亲结点的下标为 ⌊(k-1)/2⌋。注:从上到下从左到右编号是从0开始的。 

二叉树的主要性质:

  1. 在二叉树的第i层上至多有2i-1个结点。
  2. 高度为h的二叉树上最多有2h-1个结点。
  3. 任意一棵二叉树中,若叶子结点的个数为n0,度为1的结点个数为n1,度为2的结点个数为n2,则有n0=n2+1.
  4. 包含n各结点的二叉树的高度值最小为⌈log2(n+1)⌉,最大为n 假定对一棵有n个结点的完全二叉树中的结点,按层次从上到下,每层结点从左到右的顺序,从0到n-1编号,设树中某个结点的编号为i,则有:
    1. 当i=0时,无双亲,为二叉树的根;当i>0时,双亲的结点编号为⌊(i-1)/2⌋
    2. 当2i+1<n,则其左孩子存在并且其编号为2i+1,否则该结点无左孩子;
    3. 当2(i+1)<n,则其右孩子存在并且其编号为(2i+1),否则该结点无右孩子。

二叉树的抽象数据类型

二叉树的顺序存储和链式存储表示

二叉树的遍历

首先需要限制先左后右的遍历顺序。由二叉树的递归定义得到二叉树的递归遍历算法。

  • 先序遍历
  • 中序遍历
  • 后序遍历
  • 层次遍历

以上存储和遍历的代码可以查看我的有关二叉树的实验博客

线索二叉树的基本概念和构造

  二叉树的遍历算法是将非线性的二叉树进行线性化表示,在遍历后得到的线性序列中每个结点在该序列中有唯一的前驱和后继,这与线性结构类似。为了在二叉链表结构中获得前驱和后继信息,引入线索二叉树结构来表示。

  线索就是指向前去和后继结点的指针。

  有些时候,百度百科真是个好东西哦,看这里

  

在画先序/中序/后序线索树之前,可以先进行二叉树的先序/中序/后序遍历操作,然后依照每个结点唯一的前驱和后继进行画线操作。

 

树、森林与二叉树的转换

文字叙述太麻烦,按照老师的方法,图示吧。

 

堆和优先权队列

分为最小堆和最大堆,堆顶结点是所有结点中最小或最大的。

向下调整运算和建堆运算,可以参考这篇博客

优先权队列 是0个或多个元素的集合,每个元素都有一个优先权或值。

  操作主要有查找、插入和删除。

  代码为

#include<stdio.h>
#include<stdlib.h>
#define MinPQSize (10)
#define MinData (-32767)
#define Error(Str) FatalError(Str)
#define FatalError(Str) fprintf(stderr,"%s\n",Str),exit(1)
//在最小优先权队列中,查找操作用来搜索优先权最小的元素,删除操作用来删除该元素;
//在最大优先权队列中,查找操作用来搜索优先权最大的元素,删除操作用来删除该元素。
//堆是一种能有效实现优先权对立的数据结构。
//本组代码以最小堆为例
typedef int ElemType;
struct HeapStruct
{
    ElemType *Elements;
    int Capacity;
    int Size;
};
typedef struct HeapStruct *PriorityQueue;
PriorityQueue Initialize(int MaxNumber)
{
    PriorityQueue H;
    if(MaxNumber<MinPQSize)
    printf("Priority queue size is too small!");
    H=malloc(sizeof(struct HeapStruct));
    if(H==NULL)
    FatalError("Out of space!");
    H->Elements=malloc((MaxNumber+1)*sizeof(ElemType));
    H->Capacity=MaxNumber;
    H->Size=0;
    H->Elements[0]=MinData;
    return H;
}
int IsEmpty(PriorityQueue H)
{
    return H->Size==0;
}
int IsFull(PriorityQueue H)
{
    return H->Size==H->Capacity;
}
void Insert(ElemType X,PriorityQueue H)
{
    int i;
    if(IsFull(H))
    {
        Error("Priority is full!");
        return;
    }
    for(i=++H->Size;H->Elements[i/2]>X;i/=2)
        H->Elements[i]=H->Elements[i/2];
        H->Elements[i]=X;
}
ElemType DeleteMin(PriorityQueue H)
{
    int i,Child;
    ElemType minElement,lastElement;
    if(IsEmpty(H))
    {
        Error("Priority is empty!");
        return H->Elements[0];
    }
    minElement=H->Elements[1];
    lastElement=H->Elements[H->Size--];
    for(i=1;i*2<=H->Size;i=Child)//找最小元素
    {
        Child=i*2;
        if(Child!=H->Size&&H->Elements[Child+1]<H->Elements[Child])
            Child++;
        if(lastElement>H->Elements[Child])
            H->Elements[i]=H->Elements[Child];
        else
            break;
    }
    H->Elements[i]=lastElement;
    return minElement;
}
void MakeEmpty(PriorityQueue H)
{
    H->Size=0;
}
ElemType FindMin(PriorityQueue H)
{
    if(!IsEmpty(H))
         return H->Elements[1];
    Error("Priority Queue is Empty");
    return H->Elements[0];
}


void Destroy(PriorityQueue H)
{
    free(H->Elements);
    free(H);
}
int main()
{
    PriorityQueue H=Initialize(20);
    int ar[]={35,24,15,22,38,18,62,60,20,11};
    int i;
    for(i=0;i<10;i++)
         Insert(ar[i],H);
    for(i=0;i<10;i++)
    {
        printf("%d\n",DeleteMin(H));
    }
    return 0;
}

 

哈夫曼树及其应用

哈夫曼算法:

给定N个权值,{w1,w2,w3,···,wn}

  1. 用给定的权值构建N个左右子树都为空的二叉树,N棵形成森林;
  2. 从中选取权值最小和次最小的两棵树作为左右子树,构成新的二叉树,新二叉树的权值为两棵二叉树权值之和。我们约定左子树的权值要小于右子树的权值;
  3. 从原权值集合中删除在上一步中使用的两棵子树,并将新子树加入集合;
  4. 循环执行,最后可得一棵哈夫曼树。

代码如下:

构造哈夫曼树.c

/*构造哈夫曼树的算法*/
/*哈夫曼树,又称最优树,是一类带权路径长度最短的树。
树的带权路径长度,是树中所有叶子 节点的带权路径长度之和。
通常记做WPL=W1*L1+W2*L2+...+Wn*Ln。*/
#include<stdio.h>  
#include<stdlib.h>  
typedef int ElemType;  
struct BTreeNode  
{  
    ElemType data;  
    struct BTreeNode* left;  
    struct BTreeNode* right;  
};  
  
//1、输出二叉树,可在前序遍历的基础上修改。采用广义表格式,元素类型为int  
void PrintBTree_int(struct BTreeNode* BT)  
{  
    if (BT != NULL)  
    {  
        printf("%d", BT->data); //输出根结点的值  
        if (BT->left != NULL || BT->right != NULL)  
        {  
            printf("(");  
            PrintBTree_int(BT->left); //输出左子树  
            if (BT->right != NULL)  
                printf(",");  
            PrintBTree_int(BT->right); //输出右子树  
            printf(")");  
        }  
    }  
}  
  
//2、根据数组 a 中 n 个权值建立一棵哈夫曼树,返回树根指针  
struct BTreeNode* CreateHuffman(ElemType a[], int n)  
{  
    int i, j;  
    struct BTreeNode **b, *q;  
    b = malloc(n*sizeof(struct BTreeNode));  
    for (i = 0; i < n; i++) //初始化b指针数组,使每个指针元素指向a数组中对应的元素结点  
    {  
        b[i] = malloc(sizeof(struct BTreeNode));  
        b[i]->data = a[i];  
        b[i]->left = b[i]->right = NULL;  
    }  
    for (i = 1; i < n; i++)//进行 n-1 次循环建立哈夫曼树  
    {  
        //k1表示森林中具有最小权值的树根结点的下标,k2为次最小的下标  
        int k1 = -1, k2;  
        for (j = 0; j < n; j++)//让k1初始指向森林中第一棵树,k2指向第二棵  
        {  
            if (b[j] != NULL && k1 == -1)  
            {  
                k1 = j;  
                continue;  
            }  
            if (b[j] != NULL)  
            {  
                k2 = j;  
                break;  
            }  
        }  
        for (j = k2; j < n; j++)//从当前森林中求出最小权值树和次最小  
        {  
            if (b[j] != NULL)  
            {  
                if (b[j]->data < b[k1]->data)  
                {  
                    k2 = k1;  
                    k1 = j;  
                }  
                else if (b[j]->data < b[k2]->data)  
                    k2 = j;  
            }  
        }  
        //由最小权值树和次最小权值树建立一棵新树,q指向树根结点  
        q = malloc(sizeof(struct BTreeNode));  
        q->data = b[k1]->data + b[k2]->data;  
        q->left = b[k1];  
        q->right = b[k2];  
  
        b[k1] = q;//将指向新树的指针赋给b指针数组中k1位置  
        b[k2] = NULL;//k2位置为空  
    }  
    free(b); //删除动态建立的数组b  
    return q; //返回整个哈夫曼树的树根指针  
}  
  
//3、求哈夫曼树的带权路径长度  
ElemType WeightPathLength(struct BTreeNode* FBT, int len)//len初始为0  
{  
    if (FBT == NULL) //空树返回0  
        return 0;  
    else  
    {  
        if (FBT->left == NULL && FBT->right == NULL)//访问到叶子结点  
            return FBT->data * len;  
        else //访问到非叶子结点,进行递归调用,返回左右子树的带权路径长度之和,len递增  
            return WeightPathLength(FBT->left,len+1)+WeightPathLength(FBT->right,len+1);  
    }  
}  
  
//4、哈夫曼编码(可以根据哈夫曼树带权路径长度的算法基础上进行修改)  
void HuffManCoding(struct BTreeNode* FBT, int len)//len初始值为0  
{  
    static int a[10];//定义静态数组a,保存每个叶子的编码,数组长度至少是树深度减一  
    if (FBT != NULL)//访问到叶子结点时输出其保存在数组a中的0和1序列编码  
    {  
        if (FBT->left == NULL && FBT->right == NULL)  
        {  
            int i;  
            printf("结点权值为%d的编码:", FBT->data);  
            for (i = 0; i < len; i++)  
                printf("%d", a[i]);  
            printf("\n");  
        }  
        else//访问到非叶子结点时分别向左右子树递归调用,并把分支上的0、1编码保存到数组a  
        {   //的对应元素中,向下深入一层时len值增1  
            a[len] = 0;  
            HuffManCoding(FBT->left, len + 1);  
            a[len] = 1;  
            HuffManCoding(FBT->right, len + 1);  
        }  
    }  
}  
  
//主函数  
void main()  
{  
    int n, i;  
    ElemType* a;  
    struct BTreeNode* fbt;  
    printf("从键盘输入待构造的哈夫曼树中带权叶子结点数n:");  
    while(1)  
    {  
        scanf("%d", &n);  
        if (n > 1)  
            break;  
        else  
            printf("重输n值:");  
    }  
    a = malloc(n*sizeof(ElemType));  
    printf("从键盘输入%d个整数作为权值:", n);  
    for (i = 0; i < n; i++)  
        scanf(" %d", &a[i]);  
    fbt = CreateHuffman(a, n);  
    printf("广义表形式的哈夫曼树:");  
    PrintBTree_int(fbt);  
    printf("\n");  
    printf("哈夫曼树的带权路径长度:");  
    printf("%d\n", WeightPathLength(fbt, 0));  
    printf("树中每个叶子结点的哈夫曼编码:\n");  
    HuffManCoding(fbt, 0);  
}