数据结构~查找(六)

本人学习笔记,仅供自己查阅

 


查找表 是由同一类型的数据元素(或记录)构成的集合。由于“集合”中的数据元素之间存在着完全松散的关系,因此查找表是一种非常灵便的数据结构。

 

对查找表经常进行的操作有:

1)查询某个“特定的”数据元素是否在查找表中;

2)检索某个“特定的”数据元素的各种属性;

3)在查找表中插入一个数据元素;

4)从查找表中删去某个数据元素。

 

若对查找表只作前两种统称为“查找”的操作,则称此类查找表为 静态查找表

若在查找过程中同时插入查找表中不存在的元素,或者从查找表中删除已存在的某个数据元素,则称此类表为 动态查找表

 

关键字(key)是数据元素(或记录)中某个数据项的值,用它可以标识(识别)一个数据元素(或记录)。

若此关键字可以唯一地标识一个记录,则称此关键字为主关键字(primary key)(对不同的记录,其主关键字均不同)。反之,称 用以识别若干记录的关键字为 次关键字(secondary key)。当数据元素只有一个数据项时,其关键字即为 该数据元素的值。

 

查找(Searching) 根据给定的某个值,在查找表中确定一个其关键字等于给定值的记录或数据元素。若表中存在这样的一个记录,则称 查找是成功的,此时查找的结果为给出整个记录的值,或指示该记录在查找表中的位置;若表中不存在关键字等于给定值的记录,则称 查找不成功,此时查找的结果可给出一个“空”记录或“空”指针。

 

如何进行查找?

在一个结构中查找某个数据元素的过程依赖于这个数据元素在结构中所处的地位。因此,对表进行查找的方法取决于表中数据元素依何种关系(这个关系是人为地加上的)组织在一起的。

 


静态查找表

顺序查找

顺序查找(Sequential Search)又称为 线性查找,是一种最简单的查找方法。

查找过程如下:

1)从线性表 的一端开始顺序扫描线性表,依次将扫描到的结点关键字和给定值进行比较;

2)若当前扫描到的结点关键字与给定值相等,则查找成功;

3)若扫描结束后,仍未能找到关键字等于给定值的结点,则查找失败。

 

有序查找

当静态查找表为有序表示,那么可以使用折半查找算法来查找。

折半查找(Binary Search)

前提:线性表中的 关键字值按递增或递减顺序排列。

思路:确定待查记录的范围(区间),然后逐步缩小范围直到找到 或 找不到该记录为止;

查找过程:

1)将要查找的关键字K与中间位置结点的关键字比较,中间结点把线性表分为了两个子表

2)若比较结果相等,则查找结束

3)若不相等,再根据关键字与该中间结点关键字的 比较结果确定下一步在哪个子表区间内查找;

4)如此递归下去,直至找到满足条件的结点或该线性表中没有这样的结点。

 

静态树查找

目的:解决查找概率不等的记录。一般情况下,我们都是默认各个记录等概率查找的,但是有些记录可能不等概率的,此时我们可能会首先搜索概率大的记录。

 

                                                                                                     $PH = {\sum\limits_{i =1}^n}{w_i}{h_i}$

如果只考虑查找成功的情况,则使查找性能达最佳的判定树 是其带权内路径长度之和PH值最小的二叉树,称为静态最优查找树

现构造一棵二叉树,使得二叉树的带权内路径长度PH值在所具有同样权值的二叉树中近似最小,称为次优查找树

 

次优查找树 和 最优查找树 的查找性能仅差1%~2%,而构造最优查找树花费时间代价较高。(至于如何构造最优查找树 和 次优查找树,有兴趣再了解)

 

分块查找(索引顺序查找):顺序查找的一种改进方法。

有时候,可能会遇到这样的表:整个表中的元素未必有序,但若划分为若干个块后,每一块中的所有元素均小于(或大于)其后面的所有元素,称这种为分块有序

 

对于分块有序表的查找:

首先,我们需要建立一个索引表,索引表中为每一块都设置一个索引项,每一个索引项都包含两个内容:

1)该块的起始地址

2)该块中最大(或最小)的元素

 

显然,索引表是按关键字递增或递减次序排列的,如下图所示:

 

在前面建立的索引表的基础上,我们查找一个关键字需要两个步骤:

1)在索引表查找,目的是找出关键字所属的块的位置。这里如果索引表较大的话,可以采用折半查找;

2)进入该块中,使用简单顺序表查找算法进行查找。

 


动态查找表

当查找表以顺序存储结构且需要保持有序时,若对查找表进行插入、删除或排序操作,就必须移动大量的记录,当记录很多时,这种移动的代价很大;

若查找表无序,则插入删除可无需移动大量记录,但不利于查找。

利用树的形式组织查找表,可以对查找表进行动态高效的查找。

 

二叉排序表和平衡二叉树

二叉排序树(Binary Sort Tree)具有下列性质:

1)若它的左子树不为空,则左子树上所有结点的值均小于它的根结点的值;

2)若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;

3)它的左、右子树也分别为二叉排序树。

 

BST树的查找:

  1)若根结点为空,则查找失败;

  2)根结点非空,将给定的K值与二叉排序树的根结点的关键字进行比较;

  3)若给定的K值小于BST根结点的关键字:继续在该结点的左子树进行查找;

  4)若给定的K值大于BST根结点的关键字:继续在该结点的右子树进行查找

 

BST树的插入 :

  树的结构通常不是一次生成的,而是在查找过程中,当树中不存在关键字等于给定值的结点时再进行插入。新插入的结点一定是一个新添加的叶子结点,即根据关键字与根结点的大小,将新结点(关键字的值)插入根结点的相应孩子的位置(左或者右)。

 

BST树的删除:

  对于一般的二叉树来说,删去树中的一个结点是没有意义的。因为它将使以被删结点为根的子树成为森林,破坏了整棵树的结构。

  然而,对于二叉排序树,删去树上一个结点相当于删去有序序列中的一个记录,只要在删除某个结点之后依旧保持二叉排序树的特性即可。

 

性能分析:

二叉排序树查找关键字的比较次数,等于该结点所在的层次数(查找成功);若查找不成功,其比较次数的最多 为树的深度。

对于一棵具有n个结点的树来说,深度介于 ${log_2^n} + 1$ 与 n 之间。

二叉排序树

 

平衡二叉树

平衡二叉树(Balanced Binary Tree),具有下列性质:

1)它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值不超过1;

2)左子树和右子树也都是平衡二叉树

 

平衡因子:二叉树上结点的左子树的深度减去它的右子树的深度,平衡二叉树上每个结点的平衡因子只可能是-1、0和1。

 

平衡二叉排序树:如果能构造出一棵左右子树相对“均衡”的树,则树的深度就比较小,就能体现出二叉排序树的良好性质,查找时能得到最好的效率,这就是平衡二叉排序树。

平衡二叉排序树构造的基本思想:

在构建二叉排序树的过程中,每当插入(删除)一个结点时,先检查是否因插入而破坏了平衡,如是,则找出最小不平衡树,在保持二叉排序树特性的前提下,调整最小不平衡子树中各结点之间的链接关系,进行相应的旋转,使之成为新的平衡子树。

 

B- 树 和 B+ 

常见的动态查找树有:二叉查找树(BST)、平衡二叉查找树(AVL)、红黑树(RB-Tree)、B-树/B+树

由于前面三种树都属于二叉树,因此树的高度为$log_2^n$。树查找的时间复杂度与树的高度有关,因此降低树的高度的深度自然会提高查找效率,可以使用多叉树

 

磁盘存储

一个典型的磁盘驱动器如下图所示:

磁盘是一个扁平的圆盘。盘面上有许多称为磁道的圆圈,数据就记录在这些磁道上。

当磁盘驱动器进行读/写操作时。盘片装在一个主轴上,并绕主轴高速旋转,当磁道在读/写头(又叫磁头)下通过时,就可以进行读写了。

在所有盘片上面的同一个磁道可以组成一个柱面,一个磁道对应一个柱面。

 

磁盘读/写原理和效率

磁盘块是磁盘中数据存储、检索的基本单位。同一个磁盘具有固定大小的磁盘块,一般为512字节或者4K字节。

 

磁盘上数据必须用一个三维地址唯一标识:柱面号、盘面号、块号(磁盘上的盘块)

读/写磁盘上某一指定数据具有下面三个步骤:

1)首先移动臂 根据柱面号使磁头移动到对应的柱面上,这一过程被称为 定位或查找;

2)如上图所示,所有磁头都定位到了10个盘面的10条磁道上,这时根据盘面号来指定盘面上的磁道;

3)盘面确定以后,盘片开始旋转,将指定块号的磁盘段移动到磁头下。

经过上面三个步骤,指定数据的存储位置就被找到了,可以开始读写操作。


磁盘读取数据是以盘块(block)为基本单位的。位于同一盘块中的所有数据都能被一次性全部读取出来。而磁盘IO代价主要花费在查找时间上。因此我们应该尽量将相关信息存放在同一盘块中,同一磁道中。

或者至少放在同一柱面或相邻柱面上,以求在读写信息时尽量减少磁头来回移动的次数,避免过多的查找时间

 

因此,在大规模数据存储方面,如何有效地查找磁盘中的数据,需要一种合理高效的外存数据结构,就是下面阐述的B-tree结构,以及相关变种结构。

 

B-树,即为B树,又叫平衡多路查找树。B树是为了磁盘或其它存储设备而设计的一种多叉平衡树。许多数据库系统都一般使用B树或者B树的各种变形结构。

一棵 m阶 (m路查找)的B树满足下列条件:

1)树中每个结点至多有m棵子树;

2)若根结点不是叶子结点,则至少有两棵子树;

3)除根之外的所有非终端结点至少有[m / 2]棵子树;

4)所有叶结点在同一层上。B树的叶结点可以看成一种外部结点,不包含任何信息。

5)有k个孩子的非叶子结点恰好有k-1个关键码,关键码按递增次序排列。

 

一般B树中的一个结点就占用磁盘中一个块的内容(一般为512或4096字节)。

由于在查找时,我们在B树的每一层最多访问一个结点,因此查找时间的复杂度O(h)。而一个结点对应一个磁盘块,因此读取磁盘的次数也为O(h)。

若B树的度为t,则B树的高度为 $h <= {log_t^n} / 2$,比一般的二叉树读取磁盘的次数要少得多。

 

B+树是B树的一种变形。一棵m阶的B+树和m阶的B树的差异,如下所示:

1)有n棵子树的结点中含有n个关键字;

2)所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针,且叶子结点本身依关键字的大小递增顺序连接;

3)所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键字。

 

通常在B+树上有两个头指针,一个指向根结点,另一个指向关键字最小的叶子结点。

因此,对B+树进行两种查找运算:一种是从最小关键字起顺序查找;另一种是从根结点开始,进行随机查找。

在B+树上进行随机查找、插入和删除的过程基本上与B树类似。只是在查找时,若非终端结点上的关键字等于给定值,并不终止,而是继续向下直到叶子结点。因此,在B+树中,不管查找成功与否,每次查找都是走了一条从根到叶子结点的路径。

 

B树常用于文件索引,B+树常用于数据库索引。

 

键树又称数字查找树。它是一棵度>=2的树,树中的每个结点中不是包含一个或几个关键字,而是只含有组成关键字的符号。例如,若关键字是数值,则结点中只包含一个数位;若关键字是单词,则结点中只包含一个字母字符。这种树会给某种类型关键字的表的查找带来方便。

 


哈希表

什么是哈希表?

  在前面讨论的各种结构(线性表、树等)中,记录在结构中的相对位置是随机的,和记录的关键字之间不存在确定的关系,因此,在结构中查找记录时需进行一系列和关键字的比较。这一类查找方法建立在“比较”的基础上。

  在顺序查找时,比较的结果为“=”与“!=”;在折半查找、二叉排序树查找和B-树查找时,比较的结果为“<”、“=”和“>” 3种可能。查找的效率依赖于查找过程中所进行的比较次数

 

  理想的情况是希望不经过任何比较,一次存取便能得到所查记录,那就必须在记录的存储位置和它的关键字之间建立一个确定的对应关系 f,使每个关键字和结构中一个唯一的存储位置相对应。在查找时,只要根据这个对应关系 f 找到给定值K 的像 f(K)。若结构中存在关键字和K相等的记录,则必定在 f(K) 的存储位置上,由此,不需要进行比较可直接取得所查记录。在此,我们称这个对应关系 f 为哈希(Hash)函数,按这个思想建立的表为哈希表

  • 哈希函数是一个映像,因此哈希函数的设定很灵活,只要使得任何关键字由此所得的哈希函数值都落在表长允许范围之内即可;
  • 对不同的关键字可能得到同一哈希地址,即 key1 != key2,而 f(key1) = f(key2),这种现象称 冲突(collision)。

  在一般情况下,冲突只能尽可能地减少,但不能完全避免。因此,在建造哈希表时不仅要设定一个“好”的哈希函数,而且要设定一种处理冲突的方法。

 

  根据设定的哈希函数 $H(key)$ 和 处理冲突的方法将一组关键字映像到一个有限的连续的地址集(区间)上,并以关键字在地址集中的“像” 作为记录在表中的存储位置,这种表便称为哈希表,这一映像过程称为哈希造表或散列,所得存储位置称为哈希地址或散列地址

 

参考资料:

@Ouyang_Lianjun:索引顺序表查找算法

@王照陆:数据结构-动态查找

posted on 2018-04-01 09:24  ns_zhy  阅读(207)  评论(0编辑  收藏  举报