二叉树学习总结

来源:互联网 发布:安卓开发java基础 编辑:程序博客网 时间:2024/06/16 09:59

二叉树学习总结

二叉树的性质

  1. 二叉树第 i 层之多有 2^(i-1) 个节点 (满二叉树)
  2. 深度为 k 的二叉树之多有 2^k - 1 个节点
  3. 假设二叉树度为 0 的节点(终端节点或者叶节点)的个数为 n0, 度为2的节点个数为n2,那么 n0 = n2 + 1
  4. 完全二叉树总节点数为n,深度为 <=log2n + 1 最大整数
  5. n个节点的完全二叉树,对每个节点按照顺序编号,对任意编号i
    i=1,表示根节点,无双亲节点;若 i>1,双亲为 <=log2i 的最大整数。
    2i>n ,无左孩子,否则左孩子为2i
    2i + 1>n ,无右孩子,否则右孩子为2i + 1

二叉树的遍历方法(非递归)

前序遍历

模拟递归的栈实现

void PreOrderTravel (BiTree T){    InitStack (S);    p = T;    while (p || !IsStackEmpty (S)) {        visit (p);  //访问根节点        if (p->rchild)            PushStack (S, p->rchild);        if (p->lchild)            p = p->lchild;        else            Pop(S, p);    }}

中序遍历

void InOrderTravel (BiTree T){    InitStack (S);    p = T;    while (p || !IsStackEmpty (S)) {        if (p) {            Push (S, p);            p = p->lchild;        } else {            visit (p);            Pop (S, p);            p = p->rchild;        }    }}

后序遍历

后续遍历稍微复杂一点,需要设置一个标志,用于判断节点是否被访问

void PostOrderTravel (BiTree T){    InitStack (S);    p = T;    while (p || !IsStackEmpty (S)) {        if (p->lchild && unvisit (p)) { //是否存在左孩子且左孩子未被访问,如果存在,就入栈,然后继续往左走;否则,判断右孩子,最后才是根节点            Push (S, p);            p = p->lchild;        } else if (p->rchild                && unvisit (p->rchild)) {            Push (S, p);            p = p->rchild;        } else {            visit (p);            Pop (S, p);        }    }}

线索二叉树

使用二叉链表表示二叉树时,n个节点的二叉链表存在 n+1个空链域,将这些空链域指向这些节点的前驱或者后继,这样构成的二叉链表叫做“线索链表”。

数据结构如下:

typedef enum PointerTag {Link, Thread};typedef struct BiThrNode {    TElemType data;    struct BiThrNode *lchild, *rchild;    PointerTag LTag, RTag;}BiThrNode, *BiThrTree;

LTagRTag作为线索标识,当为Link时,表示指针,说明该节点存在左孩子或者右孩子;为Thread,表示线索,指向该节点的前驱或者后继。

创建线索链表时,除了树的根节点外,创建一个线索链表头节点,该指针的左孩子,指向根节点,右孩子指针为线索,指向中序遍历最后一个节点,将最后一个节点作为头结点的后继。 同样的,中序遍历的第一个节点的前驱和最后一个节点的后继都指向线索链表的头结点。这样,就形成了一个中序线索链表环。

递归实现

借助一个 pre 变量,始终指向当前节点的前一个访问的节点,即前驱

void InOrderThread (BiTree T){    if (T) {        InOrderThread (T->lchild);        if (!T->lchild) {   //左孩子不存在,指向前驱            T->LTag = Thread;            T->rchild = pre;        }        if (!pre->rchild) { //前驱的右孩子不存在,后继指向当前节点            pre->RTag = Thread;            pre->rchild = T;        }        pre = T; //保持 pre 始终指向前驱节点        InOrderThread (T->rchild);    }}

借助栈的方式,实现非递归算法,利用中序遍历

void InOrderThread (BiTree T, BiThrTree Thrt){    Thrt->LTag = Link;    Thrt->RTag = Thread;    Thrt->rchild = Thrt;    //回指    if (!T) Thrt->lchild = Thrt;    //树为空    else {        Thrt->lchild = T;        pre = Thrt;        InitStack (S);        p = T;        while (p || !IsStackEmpty (S)) {            if (p) {                Push (S, p);                p = p->lchild;            } else {                Pop (S, p);                //创建线索,中序遍历是,是访问节点                if (!p->lchild) {                    p->LTag = Thread;                    p->rchild = pre;                }                if (!pre->rchild) {                    pre->RTag = Thread;                    pre->rchild = p;                }                pre = p;                p = p->rchild;            }        } //end while        pre->RTag = Thread;        pre->rchild = Thrt;     //最后一个节点指向线索链表头结点        Thrt->rchild = pre;    }}

哈夫曼树

哈夫曼树,即最优二叉树,数的带权路径长度WPL最小。

哈夫曼算法描述如下:
(1) 根据给定的 n 个权值 {w1, w2, w3, ... , wn}构成 n 棵二叉树的集合 F = {T1, T2, T3, ... , Tn}, 其中每棵树Ti只有一个带权为 wi的根节点,左右子树均为空。
(2) 在 F 中选取两棵根节点权值最小的树作为左右子树形成一个新的二叉树,这个二叉树的根节点的权值为这两个子树的权值之和。
(3) 将新得到的二叉树加入到 F 中,同时删除之前的那两棵树。
(4) 重复以上操作,知道 F 中只有一棵树为止。这棵树就是哈夫曼树。

数据结构表示:

typedef struct {    unsigned int weight;    unsigned int parent, lchild, rchild;    //ElemType data;}HTNode, *HuffmanTree;typedef char** HuffmanCode;

n个叶节点的哈夫曼树,共有 2n-1个节点,其中n个叶节点是已知的带权节点,n-1个非终端节点是新生成的节点。将哈夫曼树存储在2n-1维一维数组中,这有点类似于数的 “双亲表示法”,但是增加了左右孩子节点。

数组下标weightparentlchildrchild

创建哈夫曼树

void CreateHuffmanTree (HuffmanTree HT, int *w, int n){    //w存放 n 个字符的权值    if (n <= 1) return;    m = 2 * n - 1;    for (p=HT, i=1; i<=n; i++, p++, w++)    //从 1 开始        *p = {*w, 0, 0, 0}; //初始化叶节点    for (; i<=m; i++, p++)        *p = {0, 0, 0, 0};  //初始化非终端节点    for (i=n+1; i<=m; i++) {        Select (HT, i-1, S1, S2);   //从 HT中 找出权值最小的两个节点S1 和 S2        HT[S1].parent = i;        HT[S2].parent = i;        HT[i].lchild = S1;        HT[i].rchild = S2;        HT[i].weight = HT[S1].weight + HT[S2].weight;    }}

求哈夫曼编码

两种方法:

  1. 从叶节点逆向搜索
  2. 从根节点往后搜索,下面主要介绍第二种方法

void GetHuffmanFromRoot (HuffmanTree HT, HuffmanCode HC, int n){    p = 2 * n - 1;    for (i=1; i<=m; i++)        tag[i] = 0; // 0 表示从未访问过,从左孩子开始访问;1 表示已经访问了左孩子,需要访问右孩子;2表示左右孩子已经访问过了,不需要再重复访问    k = 0;    while (p) {        if (tag[p] == 0) {  //先访问左孩子            tag[p]++;            if (HT[p].lchild) {                p = HT[p].lchild;                cd[k++] = '0';  //左孩子编码为 0             } else if (HT[p].rchild == 0) { //左右孩子均为0,表示叶节点,求出了该叶节点的编码                strcpy (HC[p], cd);                tag[p]++;                p = HT[p].parent;   //回溯到上一个非终端节点                k--;            }        } else if (tag[p] == 1) {            tag[p]++;            if (HT[p].rchild) {                p = HT[p].rchild;                cd[k++] = '1';            }        } else {            p = HT[p].parent;            k--;        }    }}
0 0
原创粉丝点击