树(Tree)

image-20210111193800466

是一种逻辑结构

树是n个非空结点的有限集合,n=0时称为空树

非空树需满足:

  1. 有且只有一个称为的结点
  2. 当n>1时,其余结点可分为m(m>0)个互不相交的有限集合,每个集合的本身也是一棵树,称为根结点的子树
  3. 树可以看做是递归的数据结构
  4. 每个结点都有一个前驱结点,有0个或多个后继结点
  5. n个结点的树中只有n-1条边

术语

###祖先结点和子孙结点

例如上图的H,若要从A到H,必定要经过A-B-E,此时H前边的任意一个结点都被称为祖先结点,其他结点之后的可以成为子孙结点

双亲结点和孩子结点

上图中的H在E之下,H被称为E的孩子结点,E被称为H的双亲结点

兄弟结点

具有相同的双亲结点的结点可以并称为兄弟结点

####结点的度

树中一个结点的子结点的个数称为该结点的

子结点为该结点的下一个结点

例如 B的子结点(孩子的个数)的个数为2,仅包含E和F

树的度

树的最大度为所有结点的最大度

分支结点和叶子结点

度>0的节点称为分支结点或非终端结点,度为0的节点称为叶子结点或终端结点

除根结点外,分支结点也被称为内部节点

结点

结点的层次、高度、深度

层次:根节点定义为第一层,以此类推,直到第N层

高度:与层次相反,从最低一层向上数

深度:从上往下输,与层次一致

树的高度或者是深度为结点的最大高度或深度

有序树和无序树

当两个结点交换位置后,如果这个树是无序树,那交换后与交换前是同一种树,如果是有序树,交换后是两种树

路径

经过的结点,不包含边

上图从A—H的路径为A-B-E-H

路径长度是经过边的个数

森林

n(n>0)棵互不相交的树的集合

如上图,把A去掉,其中BCD就构成森林

树的性质

  1. 树的结点数等于所有结点的度数+1

  2. 度为m的树中第i层,其上最多有
    $$
    个结点(i>=1)
    $$
    例如第一层有1个结点,那么该层的最多结点数为
    $$
    {1}
    {1-1}=1个结点
    $$
    第二层有m个结点,那么该层最多有
    $$
    =m个结点
    $$
    因为第三层有m个结点,每个节点有m个子节点,那么
    $$
    =m^2个结点
    $$

  3. 高度为h的m叉树,至多有
    $$
    \frac{(m^h-1)}{(m-1)}个结点
    $$

  4. 具有n个结点的m叉树的最小高度为
    $$
    \log_
    {[n(m-1)+1]}
    $$


树的存储结构

双亲表示法

实现:利用结构数组,存放树的结点,每个结点包含两个域

数据域:包含数据信息

双亲域:存放双亲结点的位置

例如

数组下标0123456789
数据域RABCDEFGHK
双亲域-1000113666

image-20210114185307321

结构原型

//结点结构
typedef struct
{
    int data;
    int parents;
}tree;
//树结构
typedef struct
{
    tree node[maxSize];
    int pos,n;//存储当前位置和个数
}pTree;

特点是找双亲容易,找孩子难

孩子链表

把每个结点的孩子结点排列起来,看成一个线性表,n个结点有n个孩子链表(叶子的孩子链表为空),n个链表的头指针构成一个线性表,用顺序表存储,即将每个孩子结点都看作是双亲结点,只不过是有的没有孩子结点

每个链表的头指针就是它的双亲结点

//单链表的结构
typedef struct
{
    int child;//孩子结点的下标
    struct chNode *next;//下一个孩子结点的指针
}chNode;

//每个结点存放的数据元素
typedef struct
{
    elementType data;
    struct chNode *childFirst;//头指针
}pNode;

//树  
typedef struct
{
    pNode node[maxSize];
    int r,n;//当前位置和总的个数
}tree;

特点:找孩子容易,找双亲难

孩子兄弟表示法(二叉链表表示法或者二叉树表示法)

使用二叉链表存储,链表中包含两个指针域,一个指针域表示它的第一个孩子,另一个表示它的下一个兄弟结点

结构原型

typedef struct
{
    elementType data;
    struct tree *firstChild,*nextNohd;
}tree;

对于每一个结点,一个结点除了它的第一孩子就是他的右兄弟。

如果一个结点想要找他的孩子,则先找它的左侧结点,在继续找它的右侧结点,直到为空

树和二叉树的转换

基于孩子兄弟表示法(把兄弟当儿子看)

树和二叉树都可以用二叉链表做存储结构,以二叉链表作为媒介可以导出树与二叉树的关系

给定一棵树,都能找到唯一的与之对应的二叉树,第一个儿子在左子树,兄弟在右子树

规律

兄弟结点是右孩子,从而没有了与其双亲结点的连线

做法

  1. 加线:兄弟之间加一条线
  2. 抹线:对于每个结点,除了左孩子之外,去掉它和它其余孩子之间的连线
  3. 旋转:以树(每个子树)的根节点为轴心,将整棵树顺时针旋转45度
  4. 口诀:兄弟相连留长子

逆向操作

  1. 加线:若p结点的结点是双亲结点的左孩子,则将p的右孩子的右孩子的...右孩子 沿线找到所有的右孩子都与p的双亲节点连接起来
  2. 抹线:将之前所有双亲与右孩子的连线抹掉
  3. 调整:将结点按层次排列,形成树的结构
  4. 口诀:左孩右右连双亲,去掉原来右孩线

森林转为二叉树(二叉树和多棵树的关系)

  1. 将每棵树转换成二叉树
  2. 将树的根节点用线相连
  3. 以第一棵树的根节点为二叉树的根,以根结点为轴心,顺时针旋转,构成二叉树的结构
  4. 口诀:树变二叉根相连

二叉树转为森林

  1. 抹线:将二叉树的根节点右侧的以及右侧的右侧的线抹掉,使其变成若干棵孤立的二叉树
  2. 还原:将孤立的二叉树还原成树
  3. 口诀:去掉所有右孩线,孤立二叉再还原

树的遍历方式(三种)

先序遍历、后序遍历、层次遍历

先序遍历

如果树不为空,先访问根节点,再依次访问各个子树

后序遍历

与二叉树的后续遍历类似

层序遍历

与二叉树的方式一致

森林的遍历

森林可以看做由三部分构成的

  1. 森林中第一棵树的根节点
  2. 森林中的第一棵树的子树森林
  3. 森林中其他树构成的森林

先序遍历

若森林不为空,则

  1. 访问森林第一棵树的根节点
  2. 先序遍历森林中第一棵树的子树森林
  3. 先序遍历森林中(除第一棵树之外)其余树构成的森林
  4. 人话:从左至右对森林中的每一棵树进行先根遍历

后序遍历

若森林不为空,则

  1. 中序遍历森林中第一棵树的子树森林
  2. 访问第一棵树的根节点
  3. 中序遍历其他森林
  4. 人话:从左至右对森林中的每一棵树进行后根遍历

哈夫曼树

#include <stdio.h>
#include <stdlib.h>

typedef int elementType;
typedef struct tree
{
    elementType data;
    struct tree *l, *r, *parents;
} tree;
tree *set[100];
int n;
int set2[999];

void sort()//排序函数,按照降序进行排序,以便于取最小和次小值
{
    for (int i = 0; i < n; i++)
        for (int j = i + 1; j < n; j++)
        {
            if (set[i]->data < set[j]->data)
            {
                tree *t = set[i];
                set[i] = set[j];
                set[j] = t;
            }
        }
}

void create()//创建哈夫曼树
{
    while (n != 1)
    {
        tree *a = set[n - 1], *b = set[n - 2];//取最小和次小的
        tree *p = (tree *) malloc(sizeof(tree));//用来存储新结点
        p->data = a->data + b->data;//新跟结点是左右子树的值相加
        p->l = a;//左孩子
        p->r = b;//右孩子
        p->parents = NULL;//父母设为空
        b->parents = a->parents = p;//让它的父母指向p
        n--;
        set[n - 1] = p;
        sort();
    }
}

void print(tree *t)
{
    if (t == NULL)
        return;
    printf("%d->", t->data);
    print(t->l);
    print(t->r);
}

void findElement(tree *t, int value, tree **find)
{
    if (t == NULL)
        return;
    if (t->data == value && t->r == NULL && t->l == NULL)
        (*find) = t;
    if (t->l && t->r)
    {
        findElement(t->l, value, find);
        findElement(t->r, value, find);
    }

}

void findParents(tree *t)
{
    if (t->parents == NULL)
        return;
    if (t->parents->l->data == t->data)
        printf("0");
    else
        printf("1");
    findParents(t->parents);
}

int main()
{
    int nn;
    printf("输入元素个数:");
    scanf("%d", &n);
    nn = n;
    for (int i = 0; i < n; i++)
    {
        set[i] = (tree *) malloc(sizeof(tree));
        scanf("%d", &set[i]->data);
        set2[i] = set[i]->data;
        set[i]->parents = set[i]->l = set[i]->r = NULL;
    }
    sort();
    create();
    printf("先序遍历结果:");
    print(set[0]);
    printf("\b\b  ");
    printf("\n---编码后----\n");
    for (int i = 0; i < nn; i++)
    {
        tree *find = NULL;
        findElement(set[0], set2[i], &find);
        if (find == NULL)
            printf("未找到");
        else
        {
            printf("%d:", set2[i]);
            findParents(find);
            printf("\n");

        }
    }
    return 0;
}

哈夫曼编码

生成哈夫曼树后,编码为根节点到这个叶子结点的路径,左为0,右为1

路径长度为经过的边的个数

哈夫曼树的带权路径长度=每个频度或者本身的值 * 路径长度之和

二叉树

是树形结构一种,是一种逻辑结构,规定每个结点最多有两个孩子结点

两个孩子结点名称不同,左边的称为左孩子,右边的称为右孩子

二叉树可以为空,$$n$$个结点的二叉树最多有$$\frac{(2n)!}{n!(n+1)!}$$种形态

满二叉树

image-20210112100949911

上图就是一个满二叉树

所有的分支结点都存在左子树和右子树,所有的叶子结点都在同一层上(最后一层)
$$满二叉树的结点的个数={2}^{层数}-1$$

特点

$$
一个编号为i的结点的左孩子的编号=2\times i,右孩字的编号=2\times i+1
$$

完全二叉树

image-20210112101325544

上图是一个完全二叉树

一个高度为h的二叉树与一个高度为h的满二叉树上的结点一一对应,那么这个树就是完全二叉树。

满二叉树一定是一棵完全二叉树

完全二叉树的叶子结点仅出现在最后一层和倒数第二层,并且最后一层的叶子结点从左至右一一连续

性质

$$
1.在二叉树的第i层最多有2^
个结点,i需>=i
$$

image-20210112100949911

在此图中,可以应用此性质
$$
2.深度为k的二叉树,最多有2^k-1个结点
$$
上图满二叉树中,深度为4,总结点个数为15个,利用此公式也可以算出
$$
3.二叉树中,如果终端结点(叶子结点即度为0的结点)的个数为n_0,度为2的结点的个数为n_2,则终端结点n_0=n_2+1
$$
终端(叶子)结点的个数=度为2的结点的个数+1
$$
4.具有n个结点的完全二叉树的深度为log_2n+1取下界(取整数)
$$

$$
深度为k的满二叉树的结点n的个数为2^
-1,就代表结点为n二叉树的深度为
$$

$$
log_2(n+1),因为完全二叉树的节点个数小于等于2,又大于2-1个
$$

$$
其中2^
-1为上一层的结点个数,因此完全二叉树的深度为log_2n+1取下界
$$

  1. 对于一棵有n个结点的完全二叉树,深度为int(log2n+1),并进行编号,对于任意结点i(1<=i<=n)有:
    1. 如果i=1,则结点i是二叉树的根。如果i>1,则其双亲结点是int(i/2)
    2. 如果2*i>n,则结点i无左孩子,此时的结点i为叶子结点。如果2 *i<=n,则左孩子结点就是2 *i
    3. 如果2*i+1>n,则结点i无右孩子,否则,右孩子为2i+1

存储结构

顺序存储

可以使用一维数组存储二叉树的结点

完全二叉树

image-20210112174806790

数组01234567
a71234567

可以利用完全二叉树的性质:对于结点i,如果存在左孩子,则左孩子的编号为2i,如果存在右孩子,则右孩子的编号为2i+1

a[0]可以直接弃掉,也可以存储结点的个数

非完全二叉树

非完全二叉树也可以用数组来表示,只不过需要把不存在的结点补成一棵完全二叉树

image-20210112175224291

补成完全二叉树

image-20210112175431311

数组01234567
a71234050

将不存在的点用0表示

显然顺序存储方式会浪费掉许多空间,所以顺序存储比较适用于完全二叉树

链式存储

用链表存储二叉树,二叉树中每个结点用链表的一个链结点存储

二叉树最多有两个孩子结点,所以需要两个指针域

####结构原型

typedef struct
{
    int data;
    struct tree *leftChild,*rightChild;
}tree;

含有n个结点的二叉链表中,有n+1个空链域

遍历二叉树

二叉树可以看做是一个递归,因此可以使用递归的方法进行遍历

image-20210113110556346

二叉树分为三个区域,根据根的访问方法分为先序遍历、中序遍历、后序遍历,无论使用什么顺序进行访问根,都是先访问左子树后访问右子树

image-20210113111749220

递归遍历

先序遍历

先访问根,再依次访问左子树和右子树,时间复杂度为O(n)

遍历方法

若二叉树非空

  1. 先访问根结点
  2. 先序遍历左子树
  3. 先序遍历右子树
void preOrder(tree *t)
{
    if(t==NULL)
        return;
    visit(t);
    preOrder(t->lChild);
    preOrder(t->rChild);
}

上图的遍历顺序为1->2->4->5->3->6

中序遍历

先访问左子树,再访问根,最后访问右子树,时间复杂度为O(n)

若二叉树非空

  1. 中序遍历左子树
  2. 访问根结点
  3. 中序遍历右子树
void inOrder(tree *t)
{
	if(t==NULL)
        return;
    inOrder(t->lChild);
    visit(t);
    inOrder(t->rChild);
}

上图的遍历顺序为4->2->5->1->6->3

###后序遍历

先访问左子树,再访问右子树,最后访问根

若二叉树非空

  1. 后序遍历左子树
  2. 后序遍历右子树
  3. 访问根结点
postOrder(tree *t)
{
    if(t==NULL)
        return;
    postOrder(t->lChild);
    postOorder(t->rChild);
    visit(t);
}

上图的遍历顺序为4->5->2->6->3->1

非递归遍历

借助

中序遍历

方法

  1. 依次扫描根结点左侧的所有结点,并将其一一进栈
  2. 出栈一个结点并访问
  3. 找到其右孩子结点并进栈
  4. 扫描右孩子的左侧结点并一一进栈
  5. 反复该过程,直到栈为空
void inOrder2(tree *t)
{
    tree *p=t;
    while(p||!isEmpty(t))
    {
        if(p)
        {
            push(p,t);
            p=p->lChild;
        }
        else
        {
            pop(p,s);
            visit(p);
            p=p->rChild;
        }
    }
}

前序

  1. 依次访问左侧所有结点并进栈
  2. 出栈一个节点
  3. 访问它的右结点将其入栈
  4. 访问它的所有左节点并入栈
  5. 依次执行,直到为空
void preOrder(tree *t)
{
    tree *p=t;
    while(p||!isEmpty(t))
    {
        if(p)
        {
            push(p,s);
        	visit(p);
        	p=p->lChild;
        }
        else
        {
            pop(p,s);
            p=p->rChild;
        }
    }
}

层次遍历

从上至下,从左到右进行遍历

使用队列

若二叉树非空

  1. 将根节点入队,出队并访问
  2. 若有左子树,则让左子树的根入队
  3. 若有右子树,则让右子树的根入队
  4. 入队完成后,出队,并依次访问
  5. 重复步骤,知到队为空
void bfs(tree *t)
{
    queue *q;//初始化新的队列
    tree *p;
    enQueue(q,p);//头结点先入队
    while(!empty(q))
    {
        dequeue(q,p);
        visit(p);
        if(p->lChild)
            enqueue(q,p->lChild);
        if(p->rChild)
            enqueue(q,p->rChild);
    }
    
}

总结

#include <stdio.h>
#include <stdlib.h>

typedef char ElementType;
typedef struct TNode *Position;
typedef Position BinTree;
struct TNode
{
    ElementType Data;
    BinTree Left;
    BinTree Right;
};

BinTree CreatBinTree(); /* 实现细节忽略 */
void InorderTraversal(BinTree BT);

void PreorderTraversal(BinTree BT);

void PostorderTraversal(BinTree BT);

void LevelorderTraversal(BinTree BT);

void create(BinTree *t)
{
    char ch;
    scanf("%c", &ch);
    if (ch == '#')
        *t = NULL;
    else
    {
        (*t) = malloc(sizeof(BinTree));
        (*t)->Data = ch;
        create(&(*t)->Left);
        create(&(*t)->Right);
    }
}

int main()
{
    BinTree BT = CreatBinTree();
    printf("Inorder:");
    InorderTraversal(BT);
    printf("\n");
    printf("Preorder:");
    PreorderTraversal(BT);
    printf("\n");
    printf("Postorder:");
    PostorderTraversal(BT);
    printf("\n");
    printf("Levelorder:");
    LevelorderTraversal(BT);
    printf("\n");
    return 0;
}

/* 你的代码将被嵌在这里 */
BinTree CreatBinTree()
{
    BinTree t;
    create(&t);
    return t;
}

void InorderTraversal(BinTree BT)//中
{
    if (BT == NULL)
    {
        return;
    }

    InorderTraversal(BT->Left);
    printf(" %c", BT->Data);
    InorderTraversal(BT->Right);
}

void PreorderTraversal(BinTree BT)
{
    if (BT == NULL)
        return;
    printf(" %c", BT->Data);
    PreorderTraversal(BT->Left);
    PreorderTraversal(BT->Right);
}//先序

void PostorderTraversal(BinTree BT)
{
    if (BT == NULL)
        return;
    PostorderTraversal(BT->Left);
    PostorderTraversal(BT->Right);
    printf(" %c", BT->Data);
}//后序
//BinTree为struct *类型
void LevelorderTraversal(BinTree BT)
{
    if(BT==NULL)
        return;
    BinTree date[1000];
    int rear, front;
    rear = front = 0;
    date[rear++] = BT;
    BinTree t1, t2;
    t1=date[front]->Left;
    t2=date[front]->Right;
    while (rear!=front)
    {

        t1=date[front]->Left;
        t2=date[front]->Right;
        printf(" %c",date[front]->Data);
        front++;
        if(t1)
            date[rear++] = t1;
        if(t2)
            date[rear++] = t2;
    }
}

由遍历序列构造二叉树(推导遍历结果)

  1. 已知先序遍历序列和中序遍历序列可以确定一颗二叉树
  2. 已知中序遍历序列和后序遍历序列可以确定一棵二叉树
  3. 已知先序遍历序列和后序遍历序列,无法确定一棵二叉树

方法

先序和中序

  1. 先序遍历序列中,第一个结点是根结点
  2. 根节点可将中序遍历序列分为两部分
  3. 再确定两部分的结点,两部分中先序序列出现的第一个结点分别是左子树和右子树的根
  4. 重复该过程,便能确定一棵二叉树
/*
 * @Created by : SongXiaoxu
 * @Copyright © 2021年 by: 宋晓旭. All rights reserved
 * @Date: 2021-02-09 12:18:47
 * @LastEditTime: 2021-05-21 16:45:38
 */
#include <bits/stdc++.h>
using namespace std;
typedef struct tree
{
    int data;
    struct tree *l, *r;
} tree;
const int N = 100;
int n, center[N], front[N];

void create(tree **t, int stc, int enc, int stf, int enf)
{
    if (stc > enc || stf > enf)
        return;
    (*t) = (tree *)malloc(sizeof(tree));
    (*t)->data = front[stf];
    (*t)->l = (*t)->r = NULL;
    int pos = 0;
    while (center[pos] != (*t)->data)
        pos++;
    int len = pos - stc;
    create(&(*t)->l, stc, pos - 1, stf + 1, stf + len);
    create(&(*t)->r, pos + 1, enc, stf + len + 1, enf);
}
void print(tree *t)
{
    if (t == NULL)
        return;
    print(t->l);
    print(t->r);
	printf("%d", t->data);
}
int main()
{
    tree *tt = NULL;
    cout<<"序列长度:"<<endl;
    cin >> n;
    cout<<"请输入中序遍历序列(数字之间用空格隔开):"<<endl;
    for (int i = 0; i < n; i++)
        cin >> center[i];
        cout<<"请输入先序遍历序列(数字之间用空格隔开):"<<endl;
    for (int i = 0; i < n; i++)
        cin >> front[i];
    create(&tt, 0, n - 1, 0, n - 1);
    cout<<"后序遍历序列为:";
    print(tt);
    return 0;
}

后序和中序

  1. 后序遍历序列中,最后一个结点是根结点
  2. 根结点可以将中序遍历序列分为两部分
  3. 再根据两部分的结点,两部分中,后序序列倒数出现的第二个结点为右子树的根节点,以此类推
  4. 重复该过程,便能确定一棵二叉树
/*
 * @Created by : SongXiaoxu
 * @Copyright © 2021年 by: 宋晓旭. All rights reserved
 * @Date: 2021-02-09 12:18:47
 * @LastEditTime: 2021-05-21 17:15:21
 */
#include <bits/stdc++.h>
using namespace std;
typedef struct tree
{
    int data;
    struct tree *l, *r;
} tree;
const int N = 100;
int n, center[N], tail[N];
void print(tree *t)
{
    if (t == NULL)
        return;

    print(t->l);
    printf("%d", t->data);
    print(t->r);
}
void create(tree **t, int stc, int enc, int stt, int ent)
{
    if (stc > enc || stt > ent)
        return;
    (*t) = (tree *)malloc(sizeof(tree));
    (*t)->data = tail[ent];
    (*t)->l = (*t)->r = NULL;
    int pos = 0;
    while (center[pos] != (*t)->data)
        pos++;
    int len = pos - stc;
    create(&(*t)->l, stc, pos - 1, stt, stt + len - 1);
    create(&(*t)->r, pos + 1, enc, stt + len, ent - 1);
}

vector<int> a;
void bfs(tree *t)
{
    queue<tree *> q;
    q.push(t);
    while (!q.empty())
    {
        tree *now = q.front();
        q.pop();
        a.push_back(now->data);
        if (now->l)
            q.push(now->l);
        if (now->r)
            q.push(now->r);
    }
}
int main()
{
    tree *tt = NULL;
    cin >> n;
    for (int i = 0; i < n; i++)
        cin >>
            tail[i];
    for (int i = 0; i < n; i++)
        cin >>
            center[i];

    create(&tt, 0, n - 1, 0, n - 1);
    // print(tt);
    bfs(tt);
    for(int i=0;i<a.size();i++)
    {
        if(i==0)
            cout<<a[i];
        else
            cout<<" "<<a[i];
    }
    return 0;
}

创建二叉树

方法1

#include <stdio.h>
#include <stdlib.h>
typedef struct
{
    char ch;
    struct tree *lchild, *rchild;
} tree;
tree *create()
{
    char ch;
    scanf("%c", &ch);
    if (ch == '#')
        return NULL;
    tree *t = malloc(sizeof(tree));
    t->ch = ch;
    t->lchild = create();
    t->rchild = create();
    return t;
}
void print(tree *t)
{
    if (t == NULL)
        return;
    printf(" %c ", t->ch);
    print(t->lchild);
    print(t->rchild);
}
int main()
{
    tree *t = create();
    print(t);
    return 0;
}

方法2

void create(BinTree *t)//BinTree为struct *类型
{
    char ch;
    scanf("%c", &ch);
    if (ch == '#')
        *t = NULL;
    else
    {
        (*t) = malloc(sizeof(BinTree));
        (*t)->Data = ch;
        create(&(*t)->Left);
        create(&(*t)->Right);
    }
}
/*
 * @Created by : SongXiaoxu
 * @Copyright © 2021年 by: 宋晓旭. All rights reserved
 * @Date: 2021-02-09 12:18:47
 * @LastEditTime: 2021-02-13 17:56:07
 */
#include <stdio.h>
#include <stdlib.h>

typedef struct
{
    char value;
    struct tree *l, *r;
} tree;

void create(tree **t)
{
    char ch;
    scanf("%c", &ch);
    if (ch == '#')
    {
        *t = NULL;
    } else
    {
        (*t) = (tree *) (malloc(sizeof(tree)));
        (*t)->value = ch;
        create((tree **) &(*t)->l);
        create((tree **) &(*t)->r);
    }
}

void print(tree *t)
{
    if (t == NULL)
        return;
    printf("%c", t->value);
    print((tree *) (t->l));
    print((tree *) (t->r));
}

int main()
{
    tree *t = NULL;
    create((tree **) (&t));
    print(t);
    return 0;
}

两者除函数返回值不同之外,其他地方都相同

线索二叉树

为了解决浪费的n+1个空指针的问题

线索化

若无左子树,左指针指向它的前驱结点

若无右子树,右指针指向它的后继节点

前驱结点不是它的双亲节点!!!

先序线索二叉树

结构原型

与普通二叉树不同,需要额外的两个变量用来标记左右指针的作用,0为指向孩子结点,1为指向前驱结点或者后继节点

typedef enum{link,theread} flag;
typedef struct
{
    elementType data;
    flag lTag,rTag;
    struct tree *lChild,*rChild;
}tree;

以上称为线索链表

image-20210113201845991

中序线索二叉树是最常用的

中序线索二叉树遍历寻找前驱和后继结点的方法

#include<stdio.h>
#include <stdlib.h>

typedef enum
{
    link, theHead
} flag;
typedef struct
{
    char ch;
    struct tree *lChild, *rChild;
    int ltag, rtag;
} tree;

void create(tree **t)
{
    char ch;
    scanf("%c", &ch);
    getchar();
    if (ch == '#')
        *t = NULL;
    else
    {
        (*t) = malloc(sizeof(tree));
        (*t)->ch = ch;
        create(&(*t)->lChild);
        create(&(*t)->rChild);
    }
}

void preOrder(tree *t)
{
    if (t == NULL)
        return;
    printf(" %c ", t->ch);
    if (t->ltag != 1)
        preOrder(t->lChild);
    if (t->rtag != 1)
        preOrder(t->rChild);
}

void inorder(tree *t)
{
    if (t == NULL)
        return;
    inorder(t->lChild);
    printf(" %c ", t->ch);
    inorder(t->rChild);
}

tree *pre = NULL;

void inThread(tree **t)
{
    if (*t == NULL)
        return;
    inThread(&((*t)->lChild));
    if ((*t)->lChild == NULL)
    {
        (*t)->lChild = pre;
        (*t)->ltag = 1;
    }
    if (pre != NULL && pre->rChild == NULL)
    {
        pre->rChild = *t;
        pre->rtag = 1;
    }
    pre = *t;
    inThread(&((*t)->rChild));
}

int main()
{
    tree *t;
    create(&t);
    inThread(&t);
    preOrder(t);
//    puts("");
//    inorder(t);
    return 0;
}

因为线索化之后会有两个结点没有前驱/后继结点,因此有两个空指针,此时可以增设一个头节点,头节点的左孩子指向根节点,右孩子指向遍历序列的最后一个结点。再将两个空指针指向头节结点,最后构成一个循环链表。

Q.E.D.


念念不忘,必有回响。