【数据结构】树、二叉树、二叉查找树、AVL树、伸展树、B树与C+

2019-08-19  本文已影响0人  超级超级小天才

这是数据结构类重新复习笔记的第 三 篇,同专题的其他文章可以移步:https://www.jianshu.com/nb/39256701

树的实现

实现树时,对于每一个节点,除了存储该节点的数据以外,还需要存储一些外链。

一个典型的存储方式是:左孩子右兄弟法,即对于每一个节点,存储节点的数据、指向其孩子中最左边的孩子的指针、指向其紧邻的右侧的兄弟节点的指针。

struct TreeNode
{
    Object element;
    TreeNode * firstChild;
    TreeNode * nextSiling;
};

如下边的这棵树通过这种方式表现的结果是这样的:

采用“左子右兄弟”表示树

二叉树

二叉树(binary tree)是一棵每个节点都不能有多于两个儿子的树

二叉树的特点

二叉树的一个性质是平均二叉树的深度要比结点个数N小得多,这个性质有时很重要。分析表明,这个平均深度为O(√N),而对于特殊类型的二叉树,即二叉查找树( binary search tree),其深度的平均值是O(logN)。当然极端的树的深度也可以大到N-1。

二叉树的实现

由于二叉树的每个节点最多有两个儿子,所以可以直接连接到它们。

struct BinaryNode
{
    Object element;
    BinaryNode * left;
    BinaryNode * red;
};

二叉查找树

二叉查找树(Binary Search Trees)是二叉树,它的特点是:对于任何一个节点X,其左子树中的所有节点的值都小于该节点X,其右子树中的所有节点的值都大于该节点X。如下图中的左边就是一棵二叉查找是,而右侧不是:

二叉查找树

重要的方法与其实现

平均情况分析

操作 平均时间复杂度
isEmpty O(1)
contains O(logN)
findMin/findMax O(logN)
insert O(logN)
remove O(1)

AVL树

AVL(Adelson-Velskii and Landis)树是带有平衡条件(balance condition)的二叉查找树,这个平衡条件很容易保持并且保证了树的深度为O(logN)。

AVL树要求每个节点的左子树和右子树的高度差最多为1。

AVL树

AVL树除了插入操作外,其他所有的操作都可以最多以O(logN)的时间执行

AVL树的插入

AVL树的插入比较复杂的点在于插入的值可能会破坏树原本的平衡,所以在插入值后需要进行旋转(rotation)

旋转的情况可以根据插入后的树的情况分为如下四种:

示意图来源:https://blog.csdn.net/gabriel1026/article/details/6311339

插入后的情况 描述 旋转方式
LL-左子树左高 在左子树根节点的左子树上插入节点而破坏平衡 右旋转
RR-右子树右高 在右子树根节点的右子树上插入节点而破坏平衡 左旋转
LR-左子树右高 在左子树根节点的右子树上插入节点而破坏平衡 先左旋后右旋
RL-右子树左高 在右子树根节点的左子树上插入节点而破坏平衡 先右旋后左旋

伸展树

伸展树与摊还时间

伸展树(splay tree)保证从空树开始任意连续M此对树的操作最多花费O(MlogN)的时间,即这M次连续的操作中即使有些操作耗时长,但是也有一些耗时短的操作,使得这M个连续的操作花费的总时间最坏为O(MlogN)。

摊还(amortized):一般说来,当M次操作的序列总的最坏情形运行时间为O(M f(N))时,我们就说它的摊还(amortized)运行时间为O(f(N))。因此,一棵伸展树每次操作的摊还代价是O(logN)。经过一系列的操作,有的操作可能花费时间多一些,有的可能要少一些,不存在不好的输入队列。

如果任意特定操作可以有最坏时间界O(N),而我们仍然要求一个O(logN)的摊还时间界,那么很清楚,只要有一个结点被访问,它就必须被移动。否则,一旦我们发现一个深层的结点,就有可能不断地对它进行访问。如果这个结点不改变位置,而每次访问又花费O(N),那么M次访问将花费O(MN)的时间。这个思想和数据库中将经常访问到的数据前移以及操作系统中将经常访问的数据放入cache高速缓存等思想相同。(在许多应用中,当一个结点被访问时,它就很可能不久再被访问。研究表明,这种情况的发生比人们预料的要频繁得多。)

伸展(splaying)

伸展树的基本思想是将访问到的一个较深的节点通过旋转的方式将其旋转到根节点的位置,但是为了保证旋转过程中不让一些较浅的节点也被沦落到较深的位置,这里的伸展采用一定的策略:

一个例子

如下图,想把 K1 节点调至树根处

一个伸展树的例子

首先由K1、K2、K3构成了一个“之字形”结构,使用双旋转方式得到如下的第一次调整

一个伸展树的例子

然后由K1、K4、K5构成了一个“一字形”结构,采用跷跷板的方式做第二次调整

一个伸展树的例子

树的遍历

树的遍历分为三种

B树

B树的思想很简单,如果我们使用二叉树,树的平均深度为logN,如果我们是一个M叉树(每个节点最多有M个子树),则树的平均深度为logMN,显然这样会使得树的深度降低。

M阶B树的规范

一个5阶B树的例子

一个5阶的B树,所有的非叶子节点的儿子都在3和5之间,从而有2到4个键,根可能只有两个的儿子,L=5,因此每个树叶有3到5个数据项,要求节点一半满,保证B树不致退化成简单的二叉树

一个5阶B树的例子

向B树中插入 57 数据项:按照键索引到插入数据的位置,插入数据项

一个5阶B树的例子

再插入 55 数据项,导致该叶子节点数据超出5,所以需要分裂其父节点成为两个叶子

一个5阶B树的例子

再插入 40 数据项,引起树叶被分裂成两片然后又造成父结点的分裂

一个5阶B树的例子

从B树中删除 99 数据项导致叶子节点的数据少于3从而合并叶子,而父节点也少于3从而再一次合并

一个5阶B树的例子

标准库(SLT)中的set和map

set

set是一个排序后的容器,不允许重复。set特有的操作是高效的插入、删除和执行基本查找。set也允许使用iterator来遍历。

set的方法

map

map用来存储排序后的由键和值组成的项的集合,键必须唯一,但是多个键可以同时对应一个值,即值不需要唯一,键保持逻辑排序后的顺序

map的方法

map的方法和set很像,但是其返回值是一个键-值对: pair<KeyType, ValueType>,map支持 beginendsizeenmtyinsertfinderasefind

map还重载了数组索引的操作符 []

ValueType & operator[] ( const KeyType & key );

如果map中存在key就返回只想相应值的引用,如果不存在key就在map中插入一个默认的值,然后返回指向这个插入的默认值的引用


转载请注明出处,本文永久更新链接:https://blogs.littlegenius.xin/2019/08/19/【数据结构】三树/

上一篇 下一篇

猜你喜欢

热点阅读