分享

数据结构算法总结(三)

 橙光 2011-09-21

数据结构算法总结(三)----树和二叉树

(2011-01-19 12:39:33)
标签:

教育

分类: 数据结构
二叉树是一种树形结构,它的特点是每个结点之多只有二棵子树,并且,二叉树子树有左右之分,其次序不能任意颠倒。
1、二叉树的存储结构
(1) 顺序存储
#define MAX_TREE_SIZE 100                   // 二叉树最大结点数
typedef TElemType SqBiTree[MAX_TREE_SIZE];  // 0号单元存储根结点
SqBiTree bt;

(2) 链式存储
typedef struct BiTNode
{
    TElemType        data;
    struct BiTNode   *lchild, *rchild;   // 左右孩子指针
}BiTNode, *BiTree;

2、获得二叉树的深度
(1) 顺序结构
返回二叉树的深度
int BiTreeDepth( SqBiTree T )
    int i, j = -1;
    for(i = MAX_TREE_SIZE-1; i >= 0; i--)  // 找最后一个结点
    {    
         if(T[i] != 0)  // 0表示空结点
             break;
     }
     i++;    // 完全二叉树的结点总数
        
    do{
        j++;
    }while(i >= pow(2,j)); // 计算2的j次幂
    return j;
}
注意:pow(int a, int b)表示a的b次方。空树的深度为0。

(2)链式结构
返回二叉树的深度
int BiTreeDepth( BiTree T )
    int i, j;
    if(!T)                        // 树为空,深度为0
        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;  // 比较左右子树深度,返回较大值+1
 }
注意:本程序采用递归方式,先计算左子树深度,再计算右子树深度,选一个较大的值,然后加上根的深度1,得到树的深度。

3、中序遍历二叉树
(1)递归形式
void InOrderTraverse( BiTree T, Status(*Visit)(TElemType) )
  if(T)
  {
    InOrderTraverse(T -> lchild, Visit); // 先中序遍历左子树 
    Visit(T -> data);                    // 再访问根结点
    InOrderTraverse(T -> rchild, Visit); // 最后中序遍历右子树 
  }
}
(2)非递归形式
算法一:
Status InOrderTraverse(BiTree T, Status(*Visit)(TElemType e))
{
    InitStack(S); Push(S, T);        // 根指针进栈
    while(!StackEmpty(S))
    {
        while(GetTop(S,p) && p)     // 指针不空则一直向左走
            Push(S, p -> lchild);    // 左子树不断压栈,直到左子树为空
        Pop(S, p);                   // 将空指针退栈
        if(!StackEmpty(S))           // 栈不空则出栈访问
        
            Pop(S, p);               // p指向当前要遍历的根结点
            if(!Visit(p -> data));   // 访问栈顶元素
                return ERROR;
            Push(S, p -> rchild);    // 将栈顶元素的右子树压栈
        }
    }
    return OK;
}
算法二:
Status InOrderTraverse(BiTree T, Status(*Visit)(TElemType e))
{
    InitStack(S);  p = T;
    while(p || !StackEmpty(S))
    {
        if(p)    // 根指针进栈,遍历左子树
        {
            Push(S, p);
            p = p -> lchild;
        }
        else     // 根指针退栈,访问根结点,遍历右子树
        {
            Pop(S, p);
            if(!Visit(p -> data))
                return ERROR;
            p = p -> rchild;
        }
    }
    return OK;
}

4、层次遍历二叉树
(1)顺序结构
void LevelOrderTraverse(SqBiTree T, Status (*Visit)(TElemType))
{
    int i = MAX_TREE_SIZE - 1, j;
    while(T[i] == 0)             // 找到最后一个非空结点的序号
        i --;
    for(j = 0; j <= i; j ++)     // 从根结点起,按层次遍历二叉树
        if(T[j] != 0)            // 只遍历非空结点
            Visit(T[j]);
}
(2)链式结构
typedef struct BiTNode               // 二叉树结点 
{
    TElemType data;
    struct BiTNode  *lchild, *rchild; // 左右孩子指针 
}BiTNode, *BiTree;

typedef BiTree QElemType;              // 设队列元素为二叉树的指针类型 

typedef struct QNode                  // 链队结点
{
    QElemType data;
    struct QNode  *next;
}QNode, *QueuePtr;

typedef struct                     // 链队
{
    QueuePtr front,rear;                // 队头、队尾指针 
}LinkQueue;

void LevelOrderTraverse(BiTree T, Status (*Visit)(TElemType))
{
    LinkQueue q;    
    QElemType a;    
    if(T)
    {
        InitQueue(&q);                   // 初始化空队
        EnQueue(&q, T);                  // 根指针入队
        while(!QueueEmpty(q))           
        {
            DeQueue(&q, &a);
            Visit(a -> data);             // 访问结点
            if(a -> lchild != NULL)       // 当前结点左孩子不空,则左孩子入队
                EnQueue(&q, a -> lchild); 
            if(a -> rchild != NULL)       // 当前结点右孩子不空,则右孩子入队
                EnQueue(&q, a -> rchild);
        }
    }
}

5、建立二叉树
输入结点序列建立二叉树。
Status CreateBiTree(BiTree &T)
{
    scanf("%c", &ch);
    if(ch == ' ')  T = NULL;
    else
    {  
        if(!(T = (BiTNode *)malloc(sizeof(BiTNode)))) 
            exit(OVERFLOW);
        T -> data = ch;             // 生成根结点
        CreateBiTree(T -> lchild);  // 构造左子树
        CreateBiTree(T -> rchild);  // 构造右子树
    }
    return OK;
}

6、统计叶子结点数
void CountLeaf(BiTree T, int *count)
{
    if(T != NULL)
    {
        if((T -> lchild == NULL) && (T -> rchild == NULL))  // 左右孩子为空的结点
            (*count)++;
        CountLeaf(T -> lchild, count);
        CountLeaf(T -> rchild, count); 
    }
}

7、二叉树的二叉线索存储表示
typedef enum{ Link, Thread } PointerTag;   // Link == 0:指针,Thread == 1:线索

typedef struct BiThrNode
{
    TElemType   data;
    struct BiThrNode   *lchild, *rchild;  // 左右孩子指针
    PointerTag         LTag, RTag;        // 左右标志
}BiThrNode, *BiThrTree;

8、中序遍历线索二叉树
T指向头结点,头结点的lchild指向根结点。 
Status InOrderTraverse_Thr(BiThrTree T, Status (*Visit)(TElemType e))
{
    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;      // 由于RTag为线索,所以rchild指向后继结点
            Visit(p -> data);
        }
        p = p -> rchild;          // 当RTag不是线索时,rchild指向右子树
    }
    return OK;
}
注意:遍历线索二叉树即是一个不多找后继的线性过程,类似于双向链表的遍历。

9、中序线索化二叉树
中序遍历二叉树,并将其线索化,Thrt指向头结点。令其lchild指向二叉树的根结点,rchild指向中序遍历时访问的最后一个结点。
BiThrTree pre;                     // 全局变量,始终指向刚刚访问过的结点
Status InOrderThreading(BiThrTree &Thrt, BiThrTree T)
{
   if(!(Thrt = (BiThrTree)malloc(sizeof(BiThrNode))))
        exit(OVERFLOW);
    Thrt -> LTag = Link;
    Thrt -> RTag = Thread;          // 建立头结点
    Thrt -> rchild = Thrt;          // 右指针回指
    if(!T)  Thrt -> lchild = Thrt;  // 若二叉树为空,则左指针回指
    else
    {
        Thrt -> lchild = T;         // 左指针指向根结点
        pre = Thrt;                 // pre指向头结点
        InThreading(T);             // 中序遍历进行中序线索化
        pre -> rchild = Thrt;       // 最后一个结点的右指针指向头结点
        pre -> RTag = Thread;       // 最后一个结点线索化
        Thrt -> rchild = pre;       // 头结点的右指针指向最后一个结点
    }
    return OK;
}
void InThreading(BiThrTree p)
{
    if(p)                                // p指向当前结点
    {
        InThreading(p -> lchild);        // 左子树线索化
        if(!p -> lchild)                 // 左孩子为空
        {
            p -> LTag = Thread;          // 左指针为线索,指向前驱结点
            p -> lchild = pre;
        }
        if(!pre -> rchild)               // 右孩子为空
        {
            pre -> RTag = Thread;        // 右指针为线索,指向后继结点
            pre -> rchild = p;
        }
        pre = p;
        InThreading(p -> rchild);        // 右子树线索化
    }
}
注意:InThreading()函数为中序线索化函数。即,先线索化左子树,再线索化根结点,最后线索化右子树。

数据结构算法总结(三)----树和二叉树

10、Huffman树和Huffman编码的存储表示
typedef struct
{
    unsigned int weight;   // 叶子结点的权值
    unsigned int parent, lchild, rchild; //双亲、孩子结点的编号
}HTNode, *HuffmanTree;       // 动态分配数组存储Huffman树
typedef char **HuffmanCode; // 动态分配数组存储Huffman编码表

11、Huffman编码算法一
void HuffmanCoding(HuffmanTree *HT, HuffmanCode *HC, int *w, int n)
{   // w存放n个字符的权值(均>0),构造赫夫曼树HT,并求出n个字符的赫夫曼编码HC
    // n为赫夫曼树的叶子结点数
    int m, i, s1, s2, start;
    unsigned int c, f;
    HuffmanTree p;
    char *cd;
    if(n <= 1)  return;
    m = 2 * n - 1;   // m为赫夫曼树的总结点数
    // 建立一张二维表,列字段为weight,parent,lchild,rchild。每行为一个结点信息,共m+1行,第0行未用
    *HT = (HuffmanTree)malloc((m + 1) * sizeof(HTNode)); 
    for(p = *HT + 1, i = 1; i <= n; ++i, ++p, ++w) // 初始化赫夫曼树
    {
        (*p).weight = *w;   // w存放n个字符的权值
        (*p).parent = 0;    // parent,lchild,rchild初始值为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] = '\0';                                   // 编码结束符 
    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); // 释放工作空间 
}
注意:本算法首先根据所给结点和权值构造赫夫曼树,然后根据这个赫夫曼树从下至上进行编码。

12、Huffman编码算法二
void HuffmanCoding(HuffmanTree *HT, HuffmanCode *HC, int *w, int n)
{   // w存放n个字符的权值(均>0),构造赫夫曼树HT,并求出n个字符的赫夫曼编码HC
    // n为赫夫曼树的叶子结点数
    int m, i, s1, s2, start;
    unsigned int c, f;
    HuffmanTree p;
    char *cd;
    if(n <= 1)  return;
    m = 2 * n - 1;   // m为赫夫曼树的总结点数
    // 建立一张二维表,列字段为weight,parent,lchild,rchild。每行为一个结点信息,共m+1行,第0行未用
    *HT = (HuffmanTree)malloc((m + 1) * sizeof(HTNode)); 
    for(p = *HT + 1, i = 1; i <= n; ++i, ++p, ++w) // 初始化赫夫曼树
    {
        (*p).weight = *w;   // w存放n个字符的权值
        (*p).parent = 0;    // parent,lchild,rchild初始值为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)); // 分配求编码的工作空间 
    c = m;  cdlen = 0;
    for(i = 1; i <= m; ++i)
        (*HT)[i].weight = 0;               // 遍历赫夫曼树时用作结点状态标志 
    while(c)
    {
        if((*HT)[c].weight == 0)
        {   // 向左 
            (*HT)[c].weight = 1;
            if((*HT)[c].lchild != 0)
            {
                c = (*HT)[c].lchild;
                cd[cdlen++] = '0';
            }
            else if((*HT)[c].rchild == 0)
            {    // 登记叶子结点的字符的编码
                 (*HC)[c] = (char *)malloc((cdlen + 1) * sizeof(char));
                 cd[cdlen] = '\0';
                 strcpy((*HC)[c], cd); // 复制编码(串) 
             }
        }
        else if((*HT)[c].weight == 1)
        {    // 向右 
             (*HT)[c].weight = 2;
             if((*HT)[c].rchild != 0)
             {
                  c = (*HT)[c].rchild;
                  cd[cdlen++] = '1';
              }
        }
        else
        {   // HT[c].weight == 2,退回
            (*HT)[c].weight = 0;
             c = (*HT)[c].parent;
             --cdlen; // 退到父结点,编码长度减1 
         }
      }
      free(cd);
}
注意:赫夫曼树构造完成后,遍历整个赫夫曼树,求各个叶子结点所表示的字符的赫夫曼编码。
数据结构算法总结(三)----树和二叉树



    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多