数据结构学习 --7 查找

数据结构学习 --1 绪论
数据结构学习 --2 线性表
数据结构学习 --3 栈,队列和数组
数据结构学习 --4 串
数据结构学习 --5 树和二叉树
数据结构学习 --6 图
数据结构学习 --7 查找
数据结构学习 --8 排序

本人学习记录使用 希望对大家帮助 不当之处希望大家帮忙纠正

数据结构学习 --7 查找



在这里插入图片描述

7.1 查找的基本概念

  1. 查找。在数据集合中寻找满足某种条件的数据元素的过程为查找。查找的一般结果分为两种,一种查找成功,即在数据集合中找到满足条件的数据元素;而是查找失败。

  2. 查找表(查找结构)。用于查找的数据集合称为查找表,它由同一类型的数据元素(或记录)组成,可以是一个数组或链表等数据类型。对查找表经常进行的操作一般有4种:
    (1)查询某个特定的数据元素是否在查找表中;
    (2)检索满足条件的某个特定的数据元素的各种属性;
    (3)在查找表中插入一个数据元素;
    (4)从查找表中删除某个数据元素;

  3. 静态查找表。若一个查找表的操作只涉及上述操作(1)和(2),则无须动态地修改查找表此类查找表称为静态查找表。与此对应,需要动态地插入或删除的查找表称为动态查找表。适合静态查找表的查找方法有顺序查找、折半查找、散列查找等;适合动态查找表的查找方法有二叉排序树的查找、散列查找等。

  4. 关键宇。数据元素中唯一标识该元素的某个数据项的值,使用基于关键字的查找,查找结果应该是唯一的。例如,在由一个学生元素构成的数据集合中,学生元素中“学号”这一数据项的值唯一地标识一名学生。

  5. 平均查找长度。在查找过程中,一次查找的长度是指需要比较的关键字次数,而平均查找长度则是所有查找过程中进行关键字的比较次数的平均值

7.2 顺序查找和折半查找

7.2.1 顺序查找

顺序查找又称线性查找,他对顺序表和链表都是适用的。对于顺序表,可以通过下标递增来顺序扫描每个元素;对于链表,可通过指针next来依次扫描每个元素。顺序查找通常分为对一般的无序线性表的顺序查找和对关键字的有序的线性表的顺序查找。
1.一般线性表的顺序查找
作为一种最直观的查找方法,其基本思想是从线性表的一端开始,逐个检查关键字是否满足给定的条件。若查找到某个元素的关键字满足给定条件,则查找成功,返回该元素在线性表中的位置:若已经查找到表的另一端,但还没有查找到符合给定条件的元素,则返回查找失败的信息下面给出其算法,主要是为了说明其中引入的“哨兵”的作用。

typedef struct{ //查找表的数据结构
	ElemType *elem;//元素存储空间基址,建表时按实际长度分配,0号单元留空
	int TableLen;//表的长度
}SSTabler;
int Search Seq(SSTable ST,ElemType key){
	ST.elem[0]=key;//“哨兵”/
	for(int i=ST.TableLen; ST.elem[i]!=key;--i);/从后往前找
 	return i;//若表中不存在关键字为key的元素,将查找到i为0时退出for循环
}

通常,查找表中记录的查找概率并不相等。若能预先得知每个记录的查找概率,则应先对记录的查找概率进行排序,使表中记录按查找概率由大至小重新排列。
综上所述,顺序查找的缺点是当 n 较大时,平均查找长度较大,效率低,优点是对数据元素的存储没有要求,顺序存储或链式存储皆可。对表中记录的有序性也没有要求,无论记录是否按关键字有序,均可应用。同时还需注意,对线性的链表只能进行顺序查找。

  1. 有序表的顺序查找
    若在查找之前就已经知道表是关键字有序的,则查找失败时可以不用再比较到表的另一端就
    能返回查找失败的信息,从而降低顺序查找失败的平均查找长度。假设表 L 是按关键字从小到大排列的,查找的顺序是从前往后,待查找元素的关键字为 key;当查找到第i个元素时,发现第个元素对应的关键字小于 key,第 +1个元素对应的关键字大于 key,这时就可返回查找失败的信息,因为第i个元素之后的元素的关键字均大于 key,所以表中不存在关键字为 key 元素。

7.2.2 折半查找

折半查找又称二分查找,它仅适用于有序的顺序表。
折半查找的基本思想:首先将给定值 key 与表中中间位置的元素比较,若相等,则查找成功返回该元素的存储位置:若不等,则所需查找的元素只能在中间元素以外的前半部分或后半部分(例如,在查找表升序排列时,若给定值 key 大于中间元素,则所查找的元素只可能在后半部分)然后在缩小的范围内继续进行同样的查找,如此重复,直到找到为止,或确定表中没有所需要查找的元素,则查找不成功,返回查找失败的信息。算法如下:

int Binary_Search(SSTable L,ElemType key){
	int low=0,high=L.TableLen-1,mid;
	while(low<=high){
		mid=(low+high)/2;//取中间位置
		if(L.elem(mid]==key) return mid; //查找成功则返回所在位置
		else if(L.elem[mid]>key) high=mid-1; //从前半部分继续查找
		else  low=mid+1;//从后半部分继续查找
	}
	return -1;//查找失败,返回-1
}

因为折半查找需要方便地定位查找区域,所以它要求线性表必须具有随机存取的特性。因此,该查找法仅适合于顺序存储结构,不适合于链式存储结构,且要求元素按关键字有序排列。

7.2.3 分块查找

分块查找又称索引顺序查找,它吸取了顺序查找和折半查找各自的优点,既有动态结构,又适于快速查找。
分块查找的基本思想:将查找表分为若干子块。
块内的元素可以无序,但块间的元素是有序的,即第一个块中的最大关键字小于第二个块中的所有记录的关键字,第二个块中的最大关键字小于第三个块中的所有记录的关键字,以此类推。再建立一个索引表,索引表中的每个元素含有各块的最大关键字和各块中的第一个元素的地址,索引表按关键字有序排列。分块查找的过程分为两步:
第一步是在索引表中确定待查记录所在的块,可以顺序查找或折半查找索引表;
第二步是在块内顺序查找。

7.3 树形查找

7.3.1二叉树序数(BST)

构造一颗二叉排序树的目的并不是为了排序。而是为了提高查找、插入和删除关键字的速度,二叉树这种非线性结构也有利于插入和删除的实现。

  1. 二叉排序树的定义
    二叉排序树(也称二叉查找树)或者是一颗空树。或者是具有下列特征的二叉树:
    (1)若左子树非空,则左子树上所有结点的值均小于根结点的值。
    (2)若右子树非空,则右子树上所有结点的值均大于根结点的值。
    (3)左、右子树也分别是一颗二叉排序树。
    根据二叉排序树的定义 左子树结点值< 根结点值 < 右子树结点的值,因此对二叉排序树进行中序遍历,可以得到一个递增的有序序列。
  2. 二叉排序树的查找
    二叉排序树的查找是从根结点开始,沿某个分支逐层向下比较的过程。若二叉排序树非空,先将给定值与根结点的关键字比较,若相等,则查找成功:若不等,如果小于根结点的关键字,则在根结点的左子树上查找,否则在根结点的右子树上查找。这显然是一个递归的过程。
BSTNode *BST Search(BiTree T,ElemType key){
	while(T!=NULL&&key!=T->data){//若树空或等于根结点值,则结束循环
		if(key<T->data) T=T->lchild; //小于,则在左子树上查找
		else T=T->rchild;  //大于,则在右子树上查找
	}
	return T;
}
  1. 二叉排序树的插入
    二叉排序树作为一种动态树表,其特点是树的结构通常不是一次生成的,而是在查找过程中当树中不存在关键字值等于给定值的结点时再进行插入的。
    插入结点的过程如下:若原二叉排序树为空,则直接插入;否则,若关键字k 小于根结点值则插入到左子树,若关键字k大于根结点值,则插入到右子树。插入的结点一定是一个新添加的叶结点,且是查找失败时的查找路径上访问的最后一个结点的左孩子或右孩子。
    二叉排序树插入操作的算法描述如下:
int BST_Insert(BiTree aT,KeyType k){
	if(T--NULL){//原树为空,新插入的记录为根结点
		T=(BiTree)malloc(sizeof(BSTNode));
		T->data=k;
		T->lchild=T->rchild=NULL;
		return 1; //返回1,插入成功
	}
	else if(k==T->data)  //树中存在相同关键字的结点,插入失败
		return 0;
	else if(k<T->data) //插入到T的左子树
		return BST_Insert(T->lchild,k);
    else //插入到T的右子树
		return BST_Insert(T->rchild,k);
}
  1. 二叉排序树的构造
    构造二叉排序树的算法描述如下:
void Creat_BST(BiTree T,KeyType str[],int n){
	T=NULL;//初始时T为空树
	int i=0;
	while(i<n){//依次将每个关键字插入到二叉排序树中
		BST Insert(T,str[i]);
		i++;
	}
}
  1. 二叉排序树的删除
    在二叉排序树中删除一个结点时,不能把以该结点为根的子树上的结点都删除,必须先把被删除结点从存储二叉排序树的链表上摘下,将因删除结点而断开的二叉链表重新链接起来,同时确保二叉排序树的性质不会丢失。删除操作的实现过程按3种情况来处理
    (1)若被删除结点z是叶结点,则直接删除,不会破坏二叉排序树的性质。
    (2)若结点只有一棵左子树或右子树,则让z的子树成为z父结点的子树,替代z的位置(3)若结点有左、右两棵子树,则的直接后(或直接前驱)替代,然后从二叉排序树中删去这个直接后继(或直接前驱),这样就转换成了第一或第二种情况。

  2. 二叉排序树的查找效率分析
    二叉排序树的查找效率,主要取决于树的高度。若二叉排序树的左、右子树的高度之差的绝对值不超过 1(平二,下一节它的为 0(log2n)排是一个只有右(左孩(似于有单表),则其均为 0(n)。
    就维护表的有序性而言,二叉排序树无须移动结点,只需修改指针即可完成插入和删除操作平均执行时间为 O(log2n)。二分查找的对象是有序顺表,有插入和除结点的操作,所花的代价是 0(n)。当有序表是态查找表时,宜用顺序表作为其存储结构,而采用二分查找实现其找操作;若有序表是动态查找表,则应选择二叉排序树作为其逻辑结构。

7.3.2 平衡二叉树

二叉排序树保证平衡的基本思想如下:每当在二叉排序树中插入(或别除)一个结点时,首先检查其插入路径上的结点是否因为此次操作而导致了不平衡。若导致了不平衡,则先找到插入路径上离插入结点最近的平衡因子的绝对值大于1结点,再对以为,在保持二排序树特性的前提下,调整各结点的位置关系,使之重新达到平衡。

7.3.3 红黑树

1.红黑树的定义
为了保持 AVL 树的平衡性,插入和删除操作后,非常频地调整全树整体拓扑结构,代价较大。为此在 AVL 树的平衡标准上进一步放宽条件,引入了红黑树的结构。
一棵红黑树是满足如下红黑性质的二叉排序树:
(1)每个结点或是红色,或是黑色的。
(2)根结点是黑色的
(3)叶结点(虚构的外部结点、NULL 结点)都是黑色的
(4)不存在两个相邻的红结点(即红结点的父结点和孩子结点均是黑色的)
(5)对每个结点,从该结点到任意一个叶结点的简单路径上,所含黑结点的效量相同
在这里插入图片描述

7.4 B树和B+树

7.4.1 B树及其基本操作

所谓m阶B树是所有结点的平衡因子均等于0的m路平衡查找树。一棵m阶B树或为空树,或为满足如下特性的m叉树:
(1) 树中每个结点至多有m棵子树,即至多含有m-个关键字
(2) 若根结点不是叶结点,则至少有两棵子树。
(3) 除根结点外的所有非叶结点至少有m/2棵子树,即至少含有[m/2]-1个关键字
(4) 所有非叶结点的结构如下:
在这里插入图片描述

(5)所有的叶结点都出现在同一层次上,并且不带信息(可以视为外部结点或类似于折半查找判断树的查找失败的结点,实际上这些结点不存在,指向这些结点的指针为空)

  1. B树的高度(磁盘存取次数)
  2. B树的查找
  3. B树的插入
  4. B树的删除

7.4.2 B+树的基本概念

B+树是应数据库所需而出现的一种 B树的变形树。一m阶B+树满足下列条:
(1)每个分支结点最多有 m 子(孩子结点)
(2)非叶根结点至少有两棵子树,其他每个分支结点至少有m/2 棵子树
(3)结点的子树个数与关键字个数相等。
(4)所有叶结点包含全部关键字及指向相应记录的指针,叶结点中将关键字大小顺序排列,并且相邻叶结点按大小顺序相互链接起来。
(5)所有分支结点(可为索引的索引)中包含它的各个子结点(即下一级的索引块)中关键字的最大值及指向其子结点的指针。
m阶的B+树与m阶的B的要如下:
(1)在 B+树中,具有个关键字的结点只含有 子,即每个关键字对应一而在B 树中,具有n个关键字的结点含有n+1棵子树。
(2)在B+树中,每个结点(非内部结点的关个数n的范m/2 mSm(而根结点:1SnSm):而在B中,每个点(部结点)的关键个数n的范是m/2-1SnSm-1(根结点:1Snm-1)
(3)在 B+树中,叶结点包含了全部关键字,非叶结点中出现的关键字也会出现在叶结点中;而在 B树中,最外层的终端结点包含的关键字和其他结点包含的关键字是不重复的。
(4)在 B+树中,叶结点包含信息,所有非叶结点仅起索引作用,非叶结点中的每个索引项只含有对应子树的最大关键字和指向该子树的指针,不含有该关键字对应记录的存储地址

7.5 散列表

7.5.1 散列表的基本概念

在前面介绍的线性表和树表的查找中,记录在表中的位置与记录的关键字之间不存在确定关系,因此,在这些表中查找记录时需进行一系列的关键字比较。
这类查找方法建立在“比较”的基础上,查找的效率取决于比较的次数。
散列函数:一个把查找表中的关键字映射成该关键字对应的地址的函数,记为 Hash(key)=Addr(这里的地址可以是数组下标、索引或内存地址等)。
散列函数可能会把两个或两个以上的不同关键字映射到同一地址,称这种情况为冲突,这些发生碰撞的不同关键字称为同义词。
一方面,设计得好的散列函数应尽量减少这样的冲突:另一方面,由于这样的冲突总是不可避免的,所以还要设计好处理冲突的方法。
散列表:根据关键字而直接进行访问的数据结构。也就是说,散列表建立了关键字和存储地址之间的一种直接映射关系。
理想情况下,对散列表进行查找的时间复杂度为 0(1),即与中的个数无关。下面分别介绍常用的散列函数和处理冲突的方法。

7.5.2 散列函数的构造方法

在构造散列函数时,必须注意以下几点:
(1)散列函数的定义域必须含部需要储的关键子,而值域的范则于散列表的大小或地址范围。
(2)列数计出来的地址应该能等概率、均匀地分布在整个地址空间中,从而减少冲突的发生。
(3)散列函数应尽简单,能够在较短的时间内计算出任意一个关键字对应的散列地址。
下面介绍常用的散列函数。

  1. 直接定址法
    直接取关键字的某个线性函数为散列地址,散列函数为
    H(key)= key 或H(key)= axkey + b式中,
    a 和是常数。这种方法计算最简单,且不会产生冲突。它适合关键字的分布基本连续的情况,若关键字分布不连续,空位较多,则会造成存储空间的浪费。
  2. 除留余数法
    这是一种最简单、最常用的方法,假定散列表表长为 m,取一个不于 m 接或等于m
    的质数p,利于以下公式换成散列地址。散列函数为
    H(key)= key%p

除留余数法的关键是选好 p,使得每个关键字通过该函数转换后等概率地映射到散列空间上的任意一个地址,从而尽可能减少冲突的可能性。

  1. 数分析法
    设关键字是r进制数(如十进制数),而r个数码在各位上出现的频率不一定相同,可能在某些位上分布均匀一些,每种数码出现的机会均等;而在某些位上分布不均匀,只有某几种数码经常出现,此时应选取数码分布较为均匀的若干位作为散列地址。这种方法适合于已知的关键字集合,若更换了关键字,则需要重新构造新的散列函数
  2. 平方取中法
    顾名思义,这种方法取关键字的平方值的中间几位作为散列地址。具体取多少位要视实际情况而定。这种方法得到的散列地址与关键字的每位都有关系,因此使得散列地址分布比较均舌用于关键字的每位取值都不够均匀或均小于散列地址所需的位数。
    在不同的情况下,不同的散列函数具有不同的性能,因此不能笼统地说哪种散列函数最好,在实际选择中,采用何种构造散列函数的方法取决于关键字集合的情况,但目标是尽降低产生中突的可能性

7.5.3 处理冲突的方法

应该注意到,任何设计出来的散列函数都不可能绝对地避免冲突。为此,必须考虑在发生冲突时应该如何处理,即为产生冲突的关键字寻找下一个“空”的-Hash 地址用H表示处理冲突中第i次探测得到的散列地址,假设得到的另一个散列地址 仍然发生冲突,只得继续求下一个地址 H,以此类推,直到从不发生冲突为止,则为关键在表中的地址

  1. 开放定址法
    所谓开放定址法,是指可存放新表项的空闲地址既向它的同义词表项开放,又向它的非同义词表项开放。其数学递推公式为
    H=(H(key)+d)%m
    式中,H(key)为散列函数:i=012”(kSm-1);m 表示散列表表长:为增序列取定某一增量序列后,对应的处理方法就是确定的。有以下4种取法
    (1)线性探测法
    (2)平方探测法
    (3)双散列法
    (4)伪随机序列法
  2. 拉链法
    显然,对于不同的关键字可能会通过散列函数映射到同地址,为了避免非同义词发生冲突,可以把所有的同义词存储在一个线性链表中,这个线性链表由其散列地址唯一标识。假设散列地址为的词链表的头指针存在散列表的第个单元中,因而查找、插入和刚除操作主要在同义词链中进行。拉链法适用于经常进行插入和删除的情况。

7.5.4散列查找及性能分析

散列表的查找过程与构造散列表的过程基本一致。对于一个给定的关键字 key,根据散列函
数可以计算出其散列地址,执行步骤如下;
对间一组关键字,设定相同的散列函数,则不同的处理冲突的方法得到的散列表不同,它们的平均查找长度也不同,本例与上节采用拉链法的平均查找长度不同。从散列表的查找过程可见:
(1)然散列表在关键与记录的位置之间建立了直接映,但由于“冲突”的产生使得散列表的查找过程仍然是一个给定值和关键字进行比较的过程。因此,仍需要以平均查找长度作为衡量散列表的查找效率的度量。
(2)列决于个:列处装填因子。散列表的装填因子一般记为a,定义为一个表的装满程度,
散列表的平均查找长度依赖于散列的填因子,而不直接赖于n m直观地看,越大,表示装填的记录越“满”,发生冲突的可能性越大,反之发生冲突的可能性越小。读者应能在给出散列表的长度、元素个数及散列函数和解决冲突的方法后,在求出散列表的基础上计算出查找成功时的平均查找长度和查找不成功的平均查找长度

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小*-^-*九

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值