数据结构及算法学习(三)

哈希表

(可以将数据根据固有规则放到指定位置存储,便于下次查找)
(固有规则有很多种,比如n%key+i*i,i=0,1,2…等)
(为什么会有那个i,是因为很有可能有些位置聚一大堆数字,另一些位置就没有数字存储,这样不好。如果一个位置已经不怎么样了,便根据规则(i*i)去找下家,看看下家能不能放下自己,不行继续找(自己定次数上限))
(找的时候先查下最符合的,然后依照规则去下家找,直到找到或次数上限)
(用哈希表也是为了方便,查一个数反而要找个十多次,不现实的)
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。

基本概念

若关键字为k,则其值存放在f(k)的存储位置上。由此,不需比较便可直接取得所查记录。称这个对应关系f为散列函数,按这个思想建立的表为散列表。
对不同的关键字可能得到同一散列地址,即k1≠k2,而f(k1)=f(k2),这种现象称为碰撞(英语:Collision)。具有相同函数值的关键字对该散列函数来说称做同义词。综上所述,根据散列函数f(k)和处理碰撞的方法将一组关键字映射到一个有限的连续的地址集(区间)上,并以关键字在地址集中的“像”作为记录在表中的存储位置,这种表便称为散列表,这一映射过程称为散列造表或散列,所得的存储位置称散列地址。
若对于关键字集合中的任一个关键字,经散列函数映象到地址集合中任何一个地址的概率是相等的,则称此类散列函数为均匀散列函数(Uniform Hash function),这就是使关键字经过散列函数得到一个“随机的地址”,从而减少碰撞。

常用方法

散列函数能使对一个数据序列的访问过程更加迅速有效,通过散列函数,数据元素将被更快地定位。
实际工作中需视不同的情况采用不同的哈希函数,通常考虑的因素有:
· 计算哈希函数所需时间
· 关键字的长度
· 哈希表的大小
· 关键字的分布情况
· 记录的查找频率
1. 直接寻址法:取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a·key + b,其中a和b为常数(这种散列函数叫做自身函数)。若其中H(key)中已经有值了,就往下一个找,直到H(key)中没有值了,就放进去。
2. 数字分析法:分析一组数据,比如一组员工的出生年月日,这时我们发现出生年月日的前几位数字大体相同,这样的话,出现冲突的几率就会很大,但是我们发现年月日的后几位表示月份和具体日期的数字差别很大,如果用后面的数字来构成散列地址,则冲突的几率会明显降低。因此数字分析法就是找出数字的规律,尽可能利用这些数据来构造冲突几率较低的散列地址。
3. 平方取中法:当无法确定关键字中哪几位分布较均匀时,可以先求出关键字的平方值,然后按需要取平方值的中间几位作为哈希地址。这是因为:平方后中间几位和关键字中每一位都相关,故不同关键字会以较高的概率产生不同的哈希地址。
4. 折叠法:将关键字分割成位数相同的几部分,最后一部分位数可以不同,然后取这几部分的叠加和(去除进位)作为散列地址。数位叠加可以有移位叠加和间界叠加两种方法。移位叠加是将分割后的每一部分的最低位对齐,然后相加;间界叠加是从一端向另一端沿分割界来回折叠,然后对齐相加。
5. 随机数法:选择一随机函数,取关键字的随机值作为散列地址,通常用于关键字长度不同的场合。
6. 除留余数法:取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p,p<=m。不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。对p的选择很重要,一般取素数或m,若p选的不好,容易产生同义词

处理冲突

  1. 开放寻址法:Hi=(H(key) + di) MOD m,i=1,2,…,k(k<=m-1),其中H(key)为散列函数,m为散列表长,di为增量序列,可有下列三种取法:
    1.1. di=1,2,3,…,m-1,称线性探测再散列;
    1.2. di=1^2,-1^2,2^2,-2^2,⑶^2,…,±(k)^2,(k<=m/2)称二次探测再散列;
    1.3. di=伪随机数序列,称伪随机探测再散列。
  2. 再散列法:Hi=RHi(key),i=1,2,…,k RHi均是不同的散列函数,即在同义词产生地址冲突时计算另一个散列函数地址,直到冲突不再发生,这种方法不易产生“聚集”,但增加了计算时间。
  3. 链地址法(拉链法)
  4. 建立一个公共溢出区

查找性能

散列表的查找过程基本上和造表过程相同。一些关键码可通过散列函数转换的地址直接找到,另一些关键码在散列函数得到的地址上产生了冲突,需要按处理冲突的方法进行查找。在介绍的三种处理冲突的方法中,产生冲突后的查找仍然是给定值与关键码进行比较的过程。所以,对散列表查找效率的量度,依然用平均查找长度来衡量。
查找过程中,关键码的比较次数,取决于产生冲突的多少,产生的冲突少,查找效率就高,产生的冲突多,查找效率就低。因此,影响产生冲突多少的因素,也就是影响查找效率的因素。影响产生冲突多少有以下三个因素:
1. 散列函数是否均匀;
2. 处理冲突的方法;
3. 散列表的装填因子。
散列表的装填因子定义为:α= 填入表中的元素个数 / 散列表的长度
α是散列表装满程度的标志因子。由于表长是定值,α与“填入表中的元素个数”成正比,所以,α越大,填入表中的元素较多,产生冲突的可能性就越大;α越小,填入表中的元素较少,产生冲突的可能性就越小。
实际上,散列表的平均查找长度是装填因子α的函数,只是不同处理冲突的方法有不同的函数。

二叉堆

(和队列有一丢丢类似,都是只能一头进,只能一头出的)
(以最大堆为例子,插入操作,按照规则找到位置,判断,若比父节点大,交换,继续判断,直到为否)
(删除操作,我觉得更像pop之类的,把最大的那个数拿走,把最后那个数摆到最大那,和子节点比较,如果比子节点小,交互,如果比两个都小。按照可视化给的例子是和较大的那个交换,我觉得小的也可以)
二叉堆是一种特殊的堆,二叉堆是完全二元树(二叉树)或者是近似完全二元树(二叉树)。二叉堆有两种:最大堆和最小堆。最大堆:父结点的键值总是大于或等于任何一个子节点的键值;最小堆:父结点的键值总是小于或等于任何一个子节点的键值。
二叉堆一般用数组来表示。例如,根节点在数组中的位置是0,第n个位置的子节点分别在2n+1和 2n+2。因此,第0个位置的子节点在1和2,1的子节点在3和4。以此类推。这种存储方式便於寻找父节点和子节点

基本操作

要求

在二叉堆里我们要求:

  • 最小的元素在顶端(最大的元素在顶端)
  • 每个元素都比它的父节点小(大),或者和父节点相等。

只要满足这两个条件,其他的元素怎么排都行。
这样一“堆”东西我们在程序中怎么用呢?幸运的是,二叉堆可以用一个简单的一维数组来存储,如下图所示。
假设一个元素的位置是n(第一个元素的位置为1,而不是通常数组的第一个索引0),那么它两个子节点分别是 n × 2 和 n × 2 + 1 ,父节点是n除以2取

具体操作

添加元素

首先把要添加的元素加到数组的末尾,然后和它的父节点(位置为当前位置减去1再除以2取整(k-1)/2,比如第4个元素的父节点位置是1,第7个元素的父节点位置是3)比较,如果新元素比父节点元素大则交换这两个元素,然后再和新位置的父节点比较,直到它的父节点不再比它小,或者已经到达顶端,即第1的位置。

删除元素(只能删除位置为最上的数字)

删除元素的过程类似,只不过添加元素是“向上冒”,而删除元素是“向下沉”:删除位置1的元素,把最后一个元素移到最前面,然后和它的两个子节点比较,如果两个子节点中较小的节点小于该节点,就将它们交换,直到两个子节点都比此顶点大。
计算两个子节点的位置的公式:左子节点:2K+1、右子节点:2K+2(注:这里针对的是根节点为零的情况,若根为1,则左右分别为2K与2K+1。
比如顶点为0,那么它的左右子节点分别为1和2位置,如果顶点为1,那么 1的左右两个子节点即为2和3.以此类推

定义

主要有以下两种定义。

二元组的定义

图G是一个有序二元组(V,E),其中V称为顶集(Vertices Set),E称为边集(Edges set),E与V不相交。它们亦可写成V(G)和E(G)。
E的元素都是二元组,用(x,y)表示,其中x,y∈V。[1]

三元组的定义

图G是指一个三元组(V,E,I),其中V称为顶集,E称为边集,E与V不相交;I称为关联函数,I将E中的每一个元素映射到 。如果e被映射到(u,v),那么称边e连接顶点u,v,而u,v则称作e的端点,u,v此时关于e相邻。同时,若两条边i,j有一个公共顶点u,则称i,j关于u相邻。

分类

有/无向图

如果给图的每条边规定一个方向,那么得到的图称为有向图。在有向图中,与一个节点相关联的边有出边和入边之分。相反,边没有方向的图称为无向图。

单图

一个图如果任意两顶点之间只有一条边(在有向图中为两顶点之间每个方向只有一条边);边集中不含环,则称为单图。

基本术语

阶(Order):图G中顶集V的大小称作图G的阶。
子图(Sub-Graph):当图G’=(V’,E’)其中V‘包含于V,E’包含于E,则G’称作图G=(V,E)的子图。每个图都是本身的子图。
生成子图(Spanning Sub-Graph):指满足条件V(G’) = V(G)的G的子图G’。
导出子图(Induced Subgraph):以图G的顶点集V的非空子集V1为顶点集,以两端点均在V1中的全体边为边集的G的子图,称为V1导出的导出子图;以图G的边集E的非空子集E1为边集,以E1中边关联的顶点的全体为顶点集的G的子图,称为E1导出的导出子图。
度(Degree):一个顶点的度是指与该顶点相关联的边的条数,顶点v的度记作d(v)。
入度(In-degree)和出度(Out-degree):对于有向图来说,一个顶点的度可细分为入度和出度。一个顶点的入度是指与其关联的各边之中,以其为终点的边数;出度则是相对的概念,指以该顶点为起点的边数。
自环(Loop):若一条边的两个顶点为同一顶点,则此边称作自环。
路径(Path):从u到v的一条路径是指一个序列v0,e1,v1,e2,v2,…ek,vk,其中ei的顶点为vi及vi - 1,k称作路径的长度。如果它的起止顶点相同,该路径是“闭”的,反之,则称为“开”的。一条路径称为一简单路径(simple path),如果路径中除起始与终止顶点可以重合外,所有顶点两两不等。
行迹(Trace):如果路径P(u,v)中的边各不相同,则该路径称为u到v的一条行迹。
轨道(Track):如果路径P(u,v)中的顶点各不相同,则该路径称为u到v的一条轨道。
闭的行迹称作回路(Circuit),闭的轨称作圈(Cycle)。
(另一种定义是:walk对应上述的path,path对应上述的track。Trail对应trace。)
桥(Bridge):若去掉一条边,便会使得整个图不连通,该边称为桥。

图的基本操作

(1)创建一个图结构 CreateGraph(G)
(2)检索给定顶点 LocateVex(G,elem)
(3)获取图中某个顶点 GetVex(G,v)
(4)为图中顶点赋值 PutVex(G,v,value)
(5)返回第一个邻接点 FirstAdjVex(G,v)
(6)返回下一个邻接点 NextAdjVex(G,v,w)
(7)插入一个顶点 InsertVex(G,v)
(8)删除一个顶点 DeleteVex(G,v)
(9)插入一条边 InsertEdge(G,v,w)
(10)删除一条边 DeleteEdge(G,v,w)
(11)遍历图 Traverse(G,v)

生成树

图的生成树和森林

对于一个拥有n个顶点的无向连通图,它的边数一定多于n-1条。若从中选择n-1条边,使得无向图仍然连通,则由n个顶点及这 n-1条边(弧)组成的图被称为原无向图的生成树。显示了一个无向连通图的生成树,双线圈表示的顶点为生成树的根结点。

最小生成树

在一个图中,每条边或弧可以拥有一个与之相关的数值,我们将它称为权。这些权可以具有一定的含义,比如,表示一个顶点到达另一个顶点的距离、所花费的时间、线路的造价等等。这种带权的图通常被称作网。
图或网的生成树不是唯一的,从不同的顶点出发可以生成不同的生成树,但n个结点的生成树一定有n-1条边。通常我们将权值总和最小的生成树称为最小生成树。
构造最小生成树的方法:最初生成树为空,即没有一个结点和一条边,首先选择一个顶点作为生成树的根,然后每次从不在生成树中的边中选择一条权值尽可能小的边,为了保证加入到生成树中的边不会造成回路,与该边邻接的两个顶点必须一个已经在生成树中,一个则不在生成树中,若网中有n个顶点(这里考虑的网是一个连通无向图),则按这种条件选择n-1边就可以得到这个网的最小生成树了。详细的过程可以描述为:设置2个集合,U集合中的元素是在生成树中的结点,V-U集合中的元素是不在生成树中的顶点。首先选择一个作为生成树根结点的顶点,并将它放入U集合,然后在那些一端顶点在U集合中,而另一端顶点在V-U集合中的边中找一条权最小的边,并把这条边和那个不在U集合中的顶点加入到生成树中,即输出这条边,然后将其顶点添加到U集合中,重复这个操作n-1次。

存储结构编辑

邻接矩阵(Adjacency Matrix):

有向图的邻接矩阵
具有n个顶点的有向图可以用一个n′n的方形矩阵表示。假设该矩阵的名称为M,则当

空间复杂性

AM不幸的是需要 OV2 的大的空间复杂度,即使图形实际上是稀疏的(不是很多边)。

邻接表(Adjacency List)

边结点的结构为:
adjvex是该边或弧依附的顶点在数组中的下标,next是指向下一条边或弧结点的指针
elem是顶点内容,firstedge是指向第一条边或弧结点的指针。
邻接列表(AL)是V列表数组,每个顶点一个(通常是增加顶点数),对于每个顶点i,AL [i]存储i的邻居列表。对于加权图,我们可以存储对(相邻顶点数,该边的权重)。

空间复杂性

AL具有 OV+E 的空间复杂度,其比AM更有效,并且通常在大多数图算法中都是默认图DS。

边缘列表(Edge List)

边缘列表(EL)是具有连接顶点及其权重的边的集合。通常,这些边缘通过增加权重进行排序,例如Kruskal的最小生成树(MST)问题的算法的一部分。然而,在这种可视化中,我们通过增加顶点2,根据增加的顶点1和关系来对边进行排序。注意,无向/有向图中的双向边分别被列出一次/两次。

空间复杂性

EL具有 OE 的空间复杂性,其比AM更有效,并且与AL一样高效。

特殊图

树是与V顶点和E = V-1边缘的连接图,并且在任何顶点对之间具有一个唯一的路径。通常在无向图上定义树。
由于树只有V-1边,通常被认为是稀疏图。

完整图

完整图是一个曲线V顶点和E = V *(V-1)/ 2边,即任何一对顶点之间有一个边。通常,用K V表示完整图。
完整图是最密集的简单图。

二分图

二分图是具有V顶点的无向图,其可以被分割成尺寸为m和n的两个不相交的顶点集合,其中V = m + n。同一组的成员之间没有边缘。二分图也没有奇数周期。

等等

平面图
连通图
强连通图
有向无环图
AOV网
AOE网
完全图:每一对不同顶点间都有边相连的的图,记作Kn。
二分图:顶集,且每一条边都有一个顶点在X中,而另一个顶点在Y中。
完全二分图:二分图G中若任意两个X和Y中的顶点都有边相连。若,则图G记作Km,n。
正则图:如果图中所有顶点的度皆相等,则此图称为正则图
二叉图

二叉搜索树

二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。

原理

二叉排序树的查找过程和次优二叉树类似,通常采取二叉链表作为二叉排序树的存储结构。中序遍历二叉排序树可得到一个关键字的有序序列,一个无序序列可以通过构造一棵二叉排序树变成一个有序序列,构造树的过程即为对无序序列进行排序的过程。每次插入的新的结点都是二叉排序树上新的叶子结点,在进行插入操作时,不必移动其它结点,只需改动某个结点的指针,由空变为非空即可。搜索,插入,删除的复杂度等于树高,O(log(n)).

算法实现

查找算法

在二叉排序树b中查找x的过程为:
若b是空树,则搜索失败,否则:
若x等于b的根结点的数据域之值,则查找成功;否则:
若x小于b的根结点的数据域之值,则搜索左子树;否则:
查找右子树。
递归

插入算法

向一个二叉排序树b中插入一个结点s的算法,过程为:
若b是空树,则将s所指结点作为根结点插入,否则:
若s->data等于b的根结点的数据域之值,则返回,否则:
若s->data小于b的根结点的数据域之值,则把s所指结点插入到左子树中,否则:
把s所指结点插入到右子树中。

删除算法

在二叉排序树删去一个结点,分三种情况讨论:
若*p结点为叶子结点,即PL(左子树)和PR(右子树)均为空树。由于删去叶子结点不破坏整棵树的结构,则只需修改其双亲结点的指针即可。

若*p结点只有左子树PL或右子树PR,此时只要令PL或PR直接成为其双亲结点*f的左子树或右子树即可,作此修改也不破坏二叉排序树的特性。

若*p结点的左子树和右子树均不空。在删去*p之后,为保持其它元素之间的相对位置不变,可按中序遍历保持有序进行调整,可以有两种做法:其一是令*p的左子树为*f的左子树,*s为*f左子树的最右下的结点,而*p的右子树为*s的右子树;其二是令*p的直接前驱(或直接后继)替代*p,然后再从二叉排序树中删去它的直接前驱(或直接后继)。

特殊 自平衡二叉查找树(AVL树)

在计算机科学中,AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为一,所以它也被称为高度平衡树。查找、插入和删除在平均和最坏情况下都是O(log n)。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。AVL树得名于它的发明者 G.M. Adelson-Velsky 和 E.M. Landis,他们在 1962 年的论文 “An algorithm for the organization of information” 中发表了它。

特点

AVL树本质上还是一棵二叉搜索树,它的特点是:
1.本身首先是一棵二叉搜索树。
2.带有平衡条件:每个结点的左右子树的高度之差的绝对值(平衡因子)最多为1。
也就是说,AVL树,本质上是带了平衡功能的二叉查找树(二叉排序树,二叉搜索树)。

节点数

//节点最多的时候是满二叉树,如果认为第一层的高度为0,那么节点数最多应该是2^(h+1) -1
//把h理解成层数才是2^h-1,下面写的最多有错误
高度为 h 的 AVL 树,节点数 N 最多2^h − 1; 最少N(h)=N(h− 1) +N(h− 2) + 1。
最少节点数n 如以斐波那契数列可以用数学归纳法证明:
即:
N(0) = 1 (表示 AVL Tree 高度为0的节点总数)
N(1) = 2(表示 AVL Tree 高度为1的节点总数)
N(2) = 4(表示 AVL Tree 高度为2的节点总数)
N(h)=N(h− 1) +N(h− 2) + 1 (表示 AVL Tree 高度为h的节点总数)
节点的平衡因子是它的左子树的高度减去它的右子树的高度。带有平衡因子 1、0 或 -1 的节点被认为是平衡的。带有平衡因子 -2 或 2 的节点被认为是不平衡的,并需要重新平衡这个树。平衡因子可以直接存储在每个节点中,或从可能存储在节点中的子树高度计算出来。

操作

旋转

AVL树的基本操作一般涉及运做同在不平衡的二叉查找树所运做的同样的算法。但是要进行预先或随后做一次或多次所谓的”AVL 旋转”。
假设由于在二叉排序树上插入结点而失去平衡的最小子树根结点的指针为a(即a是离插入点最近,且平衡因子绝对值超过1的祖先结点),则失去平衡后进行进行的规律可归纳为下列四种情况:
单向右旋平衡处理LL:由于在*a的左子树根结点的左子树上插入结点,*a的平衡因子由1增至2,致使以*a为根的子树失去平衡,则需进行一次右旋转操作;
单向左旋平衡处理RR:由于在*a的右子树根结点的右子树上插入结点,*a的平衡因子由-1变为-2,致使以*a为根的子树失去平衡,则需进行一次左旋转操作;
双向旋转(先左后右)平衡处理LR:由于在*a的左子树根结点的右子树上插入结点,*a的平衡因子由1增至2,致使以*\a为根的子树失去平衡,则需进行两次旋转(先左旋后右旋)操作。
双向旋转(先右后左)平衡处理RL:由于在*a的右子树根结点的左子树上插入结点,*a的平衡因子由-1变为-2,致使以*a为根的子树失去平衡,则需进行两次旋转(先右旋后左旋)操作。

插入

向AVL树插入可以通过如同它是未平衡的二叉查找树一样把给定的值插入树中,接着自底向上向根节点折回,于在插入期间成为不平衡的所有节点上进行旋转来完成。因为折回到根节点的路途上最多有 1.5 乘 log n 个节点,而每次 AVL 旋转都耗费恒定的时间,插入处理在整体上耗费 O(log n) 时间。 在平衡的的二叉排序树Balanced BST上插入一个新的数据元素e的递归算法可描述如下: 若BBST为空树,则插入一个数据元素为e的新结点作为BBST的根结点,树的深度增1; 若e的关键字和BBST的根结点的关键字相等,则不进行; 若e的关键字小于BBST的根结点的关键字,而且在BBST的左子树中不存在和e有相同关键字的结点,则将e插入在BBST的左子树上,并且当插入之后的左子树深度增加(+1)时,分别就下列不同情况处理之:BBST的根结点的平衡因子为-1(右子树的深度大于左子树的深度,则将根结点的平衡因子更改为0,BBST的深度不变; BBST的根结点的平衡因子为0(左、右子树的深度相等):则将根结点的平衡因子更改为1,BBST的深度增1; BBST的根结点的平衡因子为1(左子树的深度大于右子树的深度):则若BBST的左子树根结点的平衡因子为1:则需进行单向右旋平衡处理,并且在右旋处理之后,将根结点和其右子树根结点的平衡因子更改为0,树的深度不变; 若e的关键字大于BBST的根结点的关键字,而且在BBST的右子树中不存在和e有相同关键字的结点,则将e插入在BBST的右子树上,并且当插入之后的右子树深度增加(+1)时,分别就不同情况处理之。

删除

从AVL树中删除可以通过把要删除的节点向下旋转成一个叶子节点,接着直接剪除这个叶子节点来完成。因为在旋转成叶子节点期间最多有 log n个节点被旋转,而每次 AVL 旋转耗费恒定的时间,删除处理在整体上耗费 O(log n) 时间。

查找

在AVL树中查找同在一般BST完全一样的进行,所以耗费 O(log n) 时间,因为AVL树总是保持平衡的。不需要特殊的准备,树的结构不会由于查询而改变。(这是与伸展树查找相对立的,它会因为查找而变更树结构。)

特殊 红黑树

红黑树(Red Black Tree) 是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。
它是在1972年由Rudolf Bayer发明的,当时被称为平衡二叉B树(symmetric binary B-trees)。后来,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的“红黑树”。
红黑树和AVL树类似,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。
它虽然是复杂的,但它的最坏情况运行时间也是非常良好的,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的n 是树中元素的数目。

树的旋转

当我们在对红黑树进行插入和删除等操作时,对树做了修改,那么可能会违背红黑树的性质。
为了保持红黑树的性质,我们可以通过对树进行旋转,即修改树中某些结点的颜色及指针结构,以达到对红黑树进行插入、删除结点等操作时,红黑树依然能保持它特有的性质(五点性质)

性质

红黑树是每个节点都带有颜色属性的二叉查找树,颜色或红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:
性质1. 节点是红色或黑色。
性质2. 根节点是黑色。
性质3 每个叶节点(NIL节点,空节点)是黑色的。
性质4 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
性质5. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
这些约束强制了红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树。
要知道为什么这些特性确保了这个结果,注意到性质4导致了路径不能有两个毗连的红色节点就足够了。最短的可能路径都是黑色节点,最长的可能路径有交替的红色和黑色节点。因为根据性质5所有最长的路径都有相同数目的黑色节点,这就表明了没有路径能多于任何其他路径的两倍长。
在很多树数据结构的表示中,一个节点有可能只有一个子节点,而叶子节点不包含数据。用这种范例表示红黑树是可能的,但是这会改变一些属性并使算法复杂。为此,本文中我们使用 “nil 叶子” 或”空(null)叶子”,如上图所示,它不包含数据而只充当树在此结束的指示。这些节点在绘图中经常被省略,导致了这些树好象同上述原则相矛盾,而实际上不是这样。与此有关的结论是所有节点都有两个子节点,尽管其中的一个或两个可能是空叶子。
术语
红黑树是一种特定类型的二叉树,它是在计算机科学中用来组织数据比如数字的块的一种结构。所有数据块都存储在节点中。这些节点中的某一个节点总是担当起始位置的功能,它不是任何节点的儿子,我们称之为根节点或根。它有最多两个”儿子”,都是它连接到的其他节点。所有这些儿子都可以有自己的儿子,以此类推。这样根节点就有了把它连接到在树中任何其他节点的路径。
如果一个节点没有儿子,我们称之为叶子节点,因为在直觉上它是在树的边缘上。子树是从特定节点可以延伸到的树的某一部分,其自身被当作一个树。在红黑树中,叶子被假定为 null 或空。
由于红黑树也是二叉查找树,它们当中每一个节点的比较值都必须大于或等于在它的左子树中的所有节点,并且小于或等于在它的右子树中的所有节点。这确保红黑树运作时能够快速的在树中查找给定的值。
用途
红黑树和AVL树一样都对插入时间、删除时间和查找时间提供了最好可能的最坏情况担保。这不只是使它们在时间敏感的应用如即时应用(real time application)中有价值,而且使它们有在提供最坏情况担保的其他数据结构中作为建造板块的价值;例如,在计算几何中使用的很多数据结构都可以基于红黑树。
红黑树在函数式编程中也特别有用,在这里它们是最常用的持久数据结构之一,它们用来构造关联数组和集合,在突变之后它们能保持为以前的版本。除了O(log n)的时间之外,红黑树的持久版本对每次插入或删除需要O(log n)的空间。
红黑树是 2-3-4树的一种等同。换句话说,对于每个 2-3-4 树,都存在至少一个数据元素是同样次序的红黑树。在 2-3-4 树上的插入和删除操作也等同于在红黑树中颜色翻转和旋转。这使得 2-3-4 树成为理解红黑树背后的逻辑的重要工具,这也是很多介绍算法的教科书在红黑树之前介绍 2-3-4 树的原因,尽管 2-3-4 树在实践中不经常使用。

穷举

在我们遇到的一些问题当中,有些问题我们不能够确切的找出数学模型,即找不出一种直接求解的方法,解决这一类问题,我们一般采用搜索的方法解决。搜索就是用问题的所有可能去试探,按照一定的顺序、规则,不断去试探,直到找到问题的解,试完了也没有找到解,那就是无解,试探时一定要试探完所有的情况(实际上就是穷举);
对于问题的第一个状态,叫初始状态,要求的状态叫目标状态。
搜索就是把规则应用于实始状态,在其产生的状态中,直到得到一个目标状态为止。
产生新的状态的过程叫扩展(由一个状态,应用规则,产生新状态的过程)
搜索的要点:(1)初始状态;
(2)重复产生新状态;
(3)检查新状态是否为目标,是结束,否转(2);[1]
如果搜索是以接近起始状态的程序依次扩展状态的,叫广度优先搜索。
如果扩展是首先扩展新产生的状态,则叫深度优先搜索。

深度优先搜索

深度优先搜索用栈(stack)来实现,整个过程可以想象成一个倒立的树形:
1、把根节点压入栈中。
2、每次从栈中弹出一个元素,搜索所有在它下一级的元素,把这些元素压入栈中。并把这个元素记为它下一级元素的前驱。
3、找到所要找的元素时结束程序。
4、如果遍历整个树还没有找到,结束程序。

广度优先搜索

广度优先搜索使用队列(queue)来实现,整个过程也可以看做一个倒立的树形:
1、把根节点放到队列的末尾。
2、每次从队列的头部取出一个元素,查看这个元素所有的下一级元素,把它们放到队列的末尾。并把这个元素记为它下一级元素的前驱。
3、找到所要找的元素时结束程序。
4、如果遍历整个树还没有找到,结束程序。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值