数据结构--Chapter9(查找)

9 查找

    查找(Search)是数据处理中最常见的一种操作,它同人们的日常工作与生活有着密切关系。例如:人们从电话号码簿中查找所需要的电话号码。利用计算机查找信息首先需要把原始数据整理成一张一张的数据表,它可以具有集合、线性表、图等任意逻辑结构;然后,把每个数据表按照一定的存储结构存入到计算机中,变为计算机可处理的“表”,诸如顺序表、链接表等;最后,再通过使用有关的查找算法在相应的存储表上查找出所需要的信息。

9.1 查找的基本概念

1. 查找

    所谓查找,就是在由一组记录组成的集合中寻找主关键字值等于给定值的某个记录,或是寻找属性值符合特定条件的某些记录。例如:在学生成绩表中,若按学号进行查找,则最多只能找到一条与之相关的记录;若按姓名查找,则可能找到多条同姓名的记录。

    下面将基于主关键字的查找形式化定义为:假设含n个记录的集合{R0,R1,...,Rn-1},其相应的主关键字序列为{K0,K1,...,Kn-1}若给定某个主关键K,查找就是在记录集合中定位满足条件Kj=K的记录的过程。若记录集合中存在满足此条件的记录,则查找成功;否则,查找失败。

2. 查找表

    查找表是一种以同一类型的记录构成的集合为逻辑结构,以查找为核心算法的数据结构,由于集合中的记录之间是没有“关系”的,并且在实现查找表时不受“关系”的约束,而是根据实际应用,按照查找的具体要求组织查找表,从而方便实现高效率的查找。因此,查找表是一种应用灵活的数据结构。

    查找表中常做的操作有:建表、查找、读表元、对表做修改操作(例如:插入和删除)。其中,查找操作是指确定满足某种条件的记录是否在查找表中;读表元操作是指读取满足某种条件的记录的各种属性。查找表分为静态查找表和动态查找表。若对查找表的操作不包括对表的修改操作,则称此类查找表为静态查找表;若在查找的同时插入了表中不存在的记录,或从查找表中删除了已存在的记录,则称此类查找表为动态查找表。

3. 平均查找长度

    由于查找的主要操作是关键字的比较,所以通常把查找过程中给定值与关键字值的比较次数的期望值作为衡量一个查找算法效率优劣的标准,也称为平均查找长度(Average Search Length),通常用ASL表示。

    对一个含n条记录的查找表,查找成功时的平均查找长度为:

                                                                    ASL = ∑pici(i=0,1,2,...,n-1)

其中,n是记录个数;pi是查找第i条记录的概率,ci是查找第i条记录是关键字值与给定值比较的次数。

9.2 静态表查找

    静态查找表可以用顺序表或线性链表加以表示,这儿只讨论顺序表上查找的实现方法,即顺序表的3种查找方法:顺序查找、二分查找和分块查找。

9.2.1 顺序查找

    顺序查找又称为线性查找,它是一种最简单、最基本的查找方法。它从顺序表的一端开始,依次将每一个数据元素的关键字值与给定值key进行比较,若某个数据元素的关键字值等于给定值key,则表明查找成功;若直到所有数据元素都比较完毕,仍找不到关键字值为key的数据元素,则表明查找失败。

    假设顺序查找的基本要求是:从顺序表r[0]到r[n-1]的n个数据元素中,顺序查找出关键字值为key的记录,若查找成功,则返回其下表;否则,返回-1。顺序查找算法描述如下:

	public int seqSearch(Comparable key) {
		int i = 0;
		int n = length();
		while (i < n && r[i].key.compareTo(key) != 0) {
			i++;
		}
		if (i < n) {
			return i;// 若查找成功,则返回该数据元素的下标i;否则,返回-1
		} else {
			return -1;
		}
	}


    在等概率情况下,其查找成功的平均查找长度为:ASL=(n+...+2+1)=(n+1)/2。时间复杂度为O(n)。

9.2.2 二分查找

    上述顺序查找算法虽然实现简单,但平均查找长度较大,特别不适合用于表长较大的查找表。若以有序表表示静态查找表,则查找过程可以基于“折半”进行。所谓“折半”也称为“二分”,故二分(binary search)又称为折半查找。作为二分查找对象的数据必须是顺序表存储的有序表,通常假设有序表是按关键字值由小到大排列有序。二分查找的基本思想:首先取整个有序表的中间记录的关键字值与给定值相比较,若相等,则查找成功;否则以位于中间位置的数据元素为分界点,将查找表分为左右两个子表,并判断呆查找的关键字值key是在左子表还是右子表再在左或右子表中重复上述步骤,直到找到关键字值为key的记录或子表长度为0。

    假设二分查找的基本要求是:从有序表r[0]到r[n-1]的n个数据元素中,查找关键字值为key的记录,若查找成功,则返回其下表;否则,返回-1。为实现二分查找,需要引进low、high和mid变量,用于分别表示带查找区域的第一条记录、最后一条记录和中间记录的数组下标。当待查表非空时,二分查找算法的主要步骤归纳如下:

1)置初值:low=0,high=n-1。

2)当low<=high时,重复执行下面的步骤:

①mid=(low+high)/2.

②若key与r[mid]的关键字值相等,则查找成功,返回mid值;否则转③

③若key小雨r[mid]的关键字值,则high=mid-1;否则low=mid+1。

3)当low>high时,查找失败,返回-1。

    具体算法描述如下:

	public int binarySearch(Comparable key) {
		if (length() > 0) {
			int low = 0;
			int high = length() - 1;
			while (low <= high) {
				int mid = (low + high) / 2;
				if (r[mid].key.compareTo(key) == 0) {
					return mid;// 查找成功
				} else if (r[mid].key.compareTo(key) > 0) {
					high = mid - 1;// 查找范围缩小到前半段
				} else {
					low = mid + 1;// 查找范围缩小到后半段
				}
			}
		}
		return -1;// 查找不成功
	}


    在等概率的情况下,二分查找的平均查找长度为:ASL=log2(n+1)-1。

    不管查找成功或失败,二分查找比顺序查找要快得多。但是,它要求线性表必须按关键字进行排序。此外,二分查找仅适用于顺序存储结构、二对于动态查找表、顺序存储的插入、删除等运算都很不方便。因此,二分查找一般适用于一经建立就很少需要进行改动二又经常需要查找的静态查找表。

9.2.3 分块查找

    分块查找又称为索引顺序查找,它是顺序查找法与二分法的一种结合,其基本思想是:首先把线性表分成若干块,在每一块中,节点的存放不一定有序,但块与块之间必须是有序的,假定按结点的关键字值递增有序,则第一块中结点的关键字值都小于第二块中任意结点的关键字值,第二块中的结点的关键字值都小于第三块中任意结点的关键字值,以此类推,最后一块中所有结点的关键字值大于前面所有块中结点的关键字值。

9.3 动态表查找

    动态查找表的特点:表结构本身是在查找过程中动态生成的,即对于给定值key,若表中存在关键字值等于key的记录,则查找成功返回;否则插入关键字值等于key的记录。动态查找表可以有不同的表示方法,这儿只讨论以各种树形结构表示时的实现方法。

9.3.1 二叉排序树

1. 二叉排序树的定义

    二叉排序树(Binary Sort Tree)或者是一棵空树,或者是一棵具有下列性质的二叉树:

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

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

3)它的左右子树也都是二叉排序树。

9.3.2 平衡二叉树

9.3.3 B-树和B+树

9.3.4 红黑树

9.4 哈希表查找

9.4.1 哈希表的定义

    哈希存储的基本思想是以关键字值为自变量,通过一定的函数关系(称为散列函数或称哈希(Hash)函数),计算出对应的函数值(称哈希地址),以这个值作为数据元素的地址,并将该数据元素存入到相应地址的存储单元中去。查找时再根据要查找的关键字采用同样的函数计算出哈希地址,然后直接到相应的存储单元中去取要找的数据元素即可。

    哈希表的具体定义描述如下;

    根据设定的哈希函数H(key)和所选中的处理冲突的方法,将一组关键字映像到一个有限的、地址连续的地址集(区间)上,并以关键字在地址集中的“像”作为相应数据元素在表中的存储位置,如此构造所得的查找表称为“哈希表”。

9.4.2 常用的哈希函数

    构造哈希函数的方法有很多,但总的原则是尽可能地将关键字集合空间均匀地映射到地址集合空间中去,同时尽可能地降低冲突发生的概率。下面介绍几种最为常用的哈希函数。

1. 除留余数法

    该方法是最为简单的一种方法。它是以一个略小于哈希地址集合中地址个数m的质数p去除关键字,取其余数作为哈希地址,即:H(key)=key%p,(p<=m)。

    使用除留余数法,选取合适的p很重要,一般要求p最好是一个小于或等于m的某个最大质数,取值关系表如下:

哈希表长度8163264128256512
最大质数7133161127251503

2. 直接地址法

    去关键字的某个线性函数值为哈希地址,即:H(key)=a*key+b(a、b为常数)。

    例如,有一关键字集合{200,400,500,700,800,900},选取哈希函数为Hash(key)=key/100,假设表长为11,则构造的哈希表如下:


    此方法对不同的关键字不会产生冲突,但是此方法产生的哈希表会造成存储空间的大量浪费。

3. 数字分析法

    对于关键字的位数比存储区域的地址码位数多的情况,可以采取对关键字的各位进行分析,丢掉分布不均匀的位,留下分布均匀的位作为哈希地址,这种方法称为数字分析法。

    有学生的生日数据如下:
年.月.日
75.10.03
75.11.23
76.03.02
76.07.12
75.04.21
76.02.15

    经分析,第一位,第二位,第三位重复的可能性大,取这三位造成冲突的机会增加,所以尽量不取前三位,取后三位比较好。此方法适合于能预先估计出全体关键字的每一位上各个数字出现的频度的情况。

4. 平方取中法

    平方取中法是取关键字平方的中间几位作为哈希地址的方法,具体取多少位视实际要求而定。此方法适合于关键字中的每一位取值都不够分散或者较分散的位数小于哈希地址所需要的位数的情况。

5. 折叠法

    折叠法是将关键字自左到右或自右到左分成位数相同的几部分,最后一部分位数可以不同,然后将这几部分叠加求和,并按哈希表的表长,取最后几位作为哈希地址,这种方法成为折叠法。

6. 随机数法

    选择一个随机数,取关键字的随机数函数值位它的哈希地址,即H(key)=random(key),其中,random为随机函数。通常,当关键字长度不相等时,采用此方法构造哈希函数较好。

9.4.3 处理冲突的方法

1. 开放定址法

    开放定址法解决冲突的基本思想:当冲突发生时,形成一个地址序列,沿着这个序列诸葛探测,直到找到一个“空”的开放地址,将发生冲突的关键字值存放到该地址中取。

    开放定址法的一般形式可表示为:

    Hi=(H(key)+di)%m   (i=1,2,...,k(k<=m-1)),其中H(key)是关键字为key的哈希函数,%为取余运算,m为哈希表长,di为每次再探测时的地址增量。根据地址增量的取法不同,可得到不同的开放定址处理冲突探测方法。

2. 链地址法

    链地址法也称为拉链法,其解决冲突的基本思想:将所有具有相同哈希地址的不同关键字元素链接到同一单链表中。若选定的哈希表长度为m,则可将哈希表定义为一个由m个头指针组成的指针数组T[0...m-1],凡是哈希地址为i的数据元素,均以结点的形式插入到以T[i]为头指针的单链表中。

3. 公共溢出区法

    公共溢出区法的基本思想:除基本的存储区(称为基本表)之外,另建一个公共溢出区(称为溢出表),当不发生冲突时,数据元素可存入基本表中;当发生冲突时,不管哈希地址是什么,数据元素都存入溢出表。查找时,对给定值K通过哈希函数计算出哈希地址i,先与基本表对应的存储单元相比较,若相等,则查找成功;否则,再到溢出表中进行查找。

4. 再哈希法

    采用再哈希法解决冲突的主要思想:当发生冲突时,再用另一个哈希函数来得到一个新的哈希地址,若再发生冲突,则再使用另一个函数,直到不发生冲突为止。这种方法不易产生“聚集”,但却增加了计算时间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值