树的应用

树的应用

树的存储结构

双亲表示法(顺序存储)

定义:每个结点中保存指向双亲的“指针”(位置下标)。根结点固定存储在0,-1表示没有双亲。

新增:直接添加无需按照逻辑上的次序。

删除:①指针设为-1。(空数据导致遍历更慢)②将最后一个结点移到待删除结点的位置。

优点:查找指定结点的双亲很方便。

缺点:查找指定结点的孩子只能从头遍历。

应把二叉树的结点编号与完全二叉树对应。,结点编号反映存储位置以及各结点间逻辑关系。

//树中最多结点数
#define MAX_TREE_SIZE 100
//树结点定义
typedef struct{
    ELemType data;
    int parent;
}PTNode
//树类型定义
typedef struct{
    PTNode nodes[MAX_TREE_SIZE];
    int n;
}PTree;
孩子表示法(顺序+链式存储)

定义:顺序存储各个结点,每个结点中保存孩子结点的链表头指针。每个结点的孩子结点都用单链表链接起来形成一个线性结构。

优点:寻找孩子结点

缺点:寻找双亲结点需要遍历n个结点中孩子链表指针域所指向的n个孩子链表。

struct CTNode{
    //孩子结点在数组中的位置
    int child;
    //下一个孩子
    struct CTNode *next;
};
typedef struct{
    ElemType data;
    struct CTNode *firstChild;
}


孩子兄弟表示法(链式存储)

树和二叉树相互转换。用二叉链表表示的树。

左指针:第一个孩子结点

右指针:孩子结点的兄弟结点

//树的存储

森林和二叉树的转换

左指针:孩子结点

右指针:兄弟结点

用二叉链表存储森林。森林中各个树的根结点视为兄弟关系。

树和森林的遍历

树的遍历

先根遍历(深度优先搜索DFS)

树的先根遍历序列与这棵树相应二叉树的先序序列相同。

若树非空,先访问根结点,再依次对每棵子树进行先根遍历。

后根遍历(深度优先搜索DFS)

树的后根遍历序列与这棵树相应二叉树的中序序列相同。

若树非空,先依次对每棵子树进行后根遍历,最后再访问根结点。

层序遍历

用队列实现(广度优先搜索BFS)

①若树非空,则根结点入队

②若队列非空,队头元素出队并访问,同时将该元素的孩子依次入队

重复②直到队列为空。

森林的遍历

由m(m≥0)棵互不相交的树集合。每棵树去掉根结点后,其各子树组成森林。

先序遍历

若森林为非空,则

①访问森林中第一棵树的根结点。

②先序遍历第一棵树中根结点的子树森林

③先序遍历第一棵树之后剩余树构成的森林

等同于依次对各个树进行先根遍历。

将森林转化为二叉树,对二叉树进行先序遍历。

中序遍历

若森林非空,则

①中序遍历森林中第一棵子树的根结点的子树森林

②访问第一棵树的根结点

③中序遍历除去第一棵树之后剩余的树构成的森林

效果等同于依次对各个树进行后根遍历。

将森林转化为二叉树,对二叉树进行中序遍历。

森林 二叉树
先根遍历 先序遍历 先序遍历
后根遍历 中序遍历 中序遍历

二叉排序树

定义

二叉查找树(BST Binary Search Tree)

一棵二叉树或者是空二叉树或者是具有如下性质的二叉树:

①左子树上所有结点的关键字均小于根结点的关键字;

②右子树上所有结点的关键字均大于根结点的关键字。

③左子树和右子树又各是一棵二叉树。

左子树结点值<根结点值<右子树结点值

进行中序遍历,可以得到一个递增的有序序列。

二叉排序树可用于元素的有序组织、搜索。

默认不允许两个结点的关键字相同。

操作

构造

不同的关键字序列可能得到同样的二叉排序树。

第一个为根结点,后续关键字大于结点右插入,小于结点左插入。

//按照str[]中的关键字建立二叉排序树
void Creat_BST(BSTree &T,int str[],int n){
    T=NULL;
    int i=0;
    //依次将每个关键字插入到二叉排序树中
    while(i<n){
        BST_Insert(T,str[i])
            i++;
    }
}

查找

左子树结点值<根结点值<右子树结点值

①若树非空,目标值与根结点的值比较。

(1)若相等,则查找成功

(2)若小于根结点,则在左子树上查找

(3)若大于根结点,则在右子树上查找

⑤查找成功返回结点指针

⑥查找失败返回NULL

//在二叉排序树种查找值为key的结点
//最坏空间复杂度O(1)
BSTNode *BST_Search(BSTree T,int key){
    //若树空或等于结点值,则结束循环
    while(T!=NULL&&key!=T->key){
        //小于则在左子树上查找
        if(key<T->key)
            T=T->lchild;
        //大于则在右子树上查找
        else T=T->rchild;
    }
    return T;
}

//递归实现
//最坏空间复杂度O(h)
BSTNode *BST_Search(BSTree T,int key){
    if(T==NULL)
        return NULL;
    if(key == T->key)
        return T;
    else if(key < T->key)
        return BSTSearch(T->lchild,key);
    else
        return BSTSearch(T->rchild,key);
}

插入

应该插入的位置一定是叶子结点,注意修改其父结点指针

//在二叉排序树插入关键字为k的新结点(递归实现)
int BST_Insert(BSTree &T,int k){
    if(T==NULL){
        //新插入结点为根结点
        T=(BSTree)malloc(sizeof(BSTNode));
        T->key=k;
        T->lchild=T->rchild=NULL;
        return 1;
    }
    else if(k==T->key)
        return 0;
    else if(k<T->key)
        return BST_Insert(T->lchild,k);
    else 
        return BST_Insert(T->rchild,k);
}

查找效率分析

查找长度:需要对比关键字的次数。

反映了查找操作时间复杂度。

平均查找长度(ASL

在查找运算中,每个结点需要比对关键字的次数之和,与关键字个数的比值。

树越矮,查找次数越少。

查找成功

若树高h,找到最下层结点需要对比n次。

最好情况:n个结点的二叉树最小高度为[(log2) n]+1,平均查找长度=O((log2)n)

最坏情况:每个结点只有一个分支,树高h=结点数n,平均查找长度=O(n)

查找失败

需要补充失败结点。

删除

始终保证:左子树结点值<根结点值<右子树结点值。

进行中序遍历,可以得到一个递增的有序序列。

被删除结点z是:

叶结点

则直接删除,不会破坏二叉排序树的性质。

只有一棵左子树或右子树

则让z的子树成为z父结点的子树,代替z的位置。

有左右两棵子树

则令z的直接后继(或直接前驱)替代z,然后从二叉排序树中删去这个直接后继(或直接前驱),这样就转换成了第一种或第二种情况。

前驱:左子树中最右下的结点。

后继:右子树中最左下的结点。

平衡二叉树

定义

平衡树(AVL树),树上任一结点的左子树和右子树的高度之差不超过1。

结点平衡因子=左子树高-右子树高。

平衡二叉树就是二叉排序树。

性质

平衡二叉树结点的平衡因子的值只可能是-1,0或1。

只要有任意结点的平衡因子绝对值大于1,就不是平衡二叉树。

保持平衡

在二叉排序树中插入新结点后如何保持平衡

从插入点往回找到第一个不平衡结点,调整以该结点为根的子树。

每次调整的对象都是最小不平衡子树。插入操作中只要将最小不平衡子树调整平衡,则其他祖先结点都会恢复平衡。

最小不平衡子树

往一个平衡二叉树(本文中均指“平衡二叉排序树”)插入新的叶子结点,从插入点由下往上,依次遍历插入点的各个祖先结点,记录第一个遍历到的 |平衡因子|≥2 (即不平衡)的祖先结点,以该结点为根结点的子树即为这棵树的最小不平衡子树

调整最小不平衡子树A

假定所有子树高度都为H(仅能为H)。

目标:①恢复平衡②保持二叉排序树特性。

为了方便讨论,我们使用连续的两个字母来表示平衡因子,以表示各种不同的情况。第一个字母表示最小不平衡子树根结点的平衡因子,第二个字母表示最小不平衡子树较高子树的根结点的平衡因子。

LL

在A的左孩子的左子树中插入导致不平衡。

平衡旋转(右单旋转)。

不平衡子树的右子树进行了旋转。

由于在结点A的左孩子(L)的左子树(L)上插入新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡,需要一次向右的旋转操作。将A的左孩子B向右上旋转代替A成为根结点,将A结点向右下旋转成为B的右子树的根结点,而B的原右子树则作为A结点的左子树。

RR

在A的右孩子的右孩子中插入导致不平衡。

左单旋转。

不平衡子树的左孩子进行了旋转。

由于在结点A的右孩子的右子树上插入新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡,需要一次向左的旋转操作。将A的右孩子B向左上旋转代替A成为根结点,将A结点向左下旋转成为B的左子树的根结点,而B的原左子树则作为A结点的右子树。

LR

在A的左孩子的右孩子中插入导致不平衡。

先左后右双旋转。

由于在A的左孩子的右子树上插入新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡,需要再进行两次旋转操作,先左旋转后右旋转。先将A结点的左孩子B的右子树的根结点C向左上旋转提升到B结点的位置,然后再把该C结点向右上旋转提升到A结点的位置。

拆分B的右子树,在B的右子树上插入新结点等同于在C的右子树上插入新结点。

RL

在A的右孩子的左子树中插入导致不平衡

posted @ 2021-12-26 12:54  wangqr  阅读(91)  评论(0)    收藏  举报