23.数据结构 查找

1.查找的基本概念

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

关键字:用来标识一个数据元素(或记录)的某个数据项的值。

主关键字:可唯一地标识一个记录的关键字。(一对一,类似学号,工号)

次关键字:反之,用以标识若干记录的关键字。(一对多,类似姓名,成绩)

若查找表中存在这样一个记录,则称“查找成功”

查找结果给出整个记录的信息,或指示该记录在查找表中的位置

否则称“查找不成功”

查找结果给出“空记录”“空指针”

对查找表经常进行的操作

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

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

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

4.删除查找表中的摸一个数据元素。

查找表可分为两类:

静态查找表:

仅作“查询”(检索)操作的查找表。

动态查找表:

“插入”“删除”操作的查找表。

有时在查询之后,还需要将“查询”结果为“不在查找表中”的数据元素插入到查找表中;或者,从查找表中删除其“查询”结果为“在查找表中”的数据元素,此类表为动态查找表。

查找算法的评价指标:关键字的平均比较次数,也称平均查找长度ASL

(关键字比较次数的期望值)

n:记录的个数

pi:查找第i个记录的概率(通常认为pi=1/n)

ci:找到第i个记录所需的比较次数


2.线性表的查找

2.1顺序查找(线性查找)

应用范围:

  顺序表或线性链表表示的静态查找表

  表内元素之间无序

顺序表的表示:

数据元素类型定义:

typedef struct
{
	KeyType key;//关键字域
	...//其他域
}ElemType;

typedef struct
{
	//顺序表结构类型定义
	ElemType* R;//表基址
	int length;//表长
}SSTable;
SSTable ST;//定义顺序表ST

 在顺序表ST中查找值为key的数据元素(从最后一个元素开始比较)

int Search_Seq(SSTable ST, KeyType key)
{
	//若成功返回其位置信息,否则返回0
	for (i = ST.length; i >= 1; i--)
	{
		if (ST.R[i].key == key)
		{
			return i;
		}
	}
	return 0;
}

//其他形式
int Search_Seq(SSTable ST, KeyType key)
{
	for (i = ST.length; ST.R[i].key != key; i--)
		if (i <= 0)
			break;
	if (i > 0)
		return i;
	else
		return 0;
}

int Search_Seq(SSTable ST, KeyType key)
{
	for (i = ST.length; ST.R[i].key != key && i>0; i--);
	if (i > 0)
		return i;
	else
		return 0;
}

顺序查找 改进:把待查关键字key存入表头(“哨兵”、“监视哨”),从后往前逐个比较,可免去查找过程中每一步都要检测释放查找完毕,加快速度。

int Search_Seq(SSTable ST, KeyType key)
{
	//设置监视哨的顺序查找
	ST.R[0].key = key;
	for (i = ST.length; ST.R[i].key != key; i--);
	return i;
}

当ST.length较大时,此改进能使进行一次查找所需的平均时间几乎减少一半。

时间效率分析:

比较次数与key位置有关:

查找第i个元素,需比较n-i+1次;

查找失败,需比较n+1次;

时间复杂度:O(n)

空间复杂度:一个辅助空间—O(1)

顺序查找的特点:

优点:算法简单,逻辑次序无要求,且不同存储结构均适用。

缺点:ASL太长,时间效率太低。

2.2折半查找(二分或对分查找)

折半查找:每次将待查记录所在区间缩小一半。

折半查找算法(非递归算法):

1.设表长为n,low、high和mid分别指向待查元素所在区间的上界、下界和中点,key为给定的要查找的值

2.初始时,令low=1,high=n,mid=(low+high)/2的向下取整

3.让k与mid指向的记录比较

若key==R[mid].key,查找成功

若key<R[mid].key,则high=mid-1

若key>R[mid].key,则low=mid+1

4.重复上述操作,直至low>high时,查找失败

int Search_Bin(SSTable ST, KeyType key)
{
	low = 1;//置区间初值
	high = ST.length;
	while (low <= high)
	{
		mid = (low + high) / 2;
		if (ST.R[mid].key == key)//找到待查元素
			return mid;
		else if (key < ST.R[mid].key)//缩小查找区间
			high = mid - 1;//继续在前半区间进行查找
		else
			low = mid + 1;//继续在后半区间进行查找
	}
	return 0;//顺序表中不存在待查元素
}

折半查找算法(递归算法):

int Search_Bin(SSTable ST, KeyType key,int low, int high)
{
	if (low > high)//查找补刀时返回0
		return 0;
	mid = (low + high) / 2;
	if (key == ST.R[mid].key)
		return mid;
	else if (key < ST.R[mid].key)
		Search_Bin(ST, key, low, mid - 1);
	else
		Search_Bin(ST, key, mid + 1, high);
	return 0;
}

折半查找的性能分析

折半查找优点:效率比顺序查找高。

这般查找缺点:只适用于有序表,且限于顺序存储结构(对线性链表无效)。

2.3分块查找(索引顺序查找)


3.树表的查找

3.1.二叉排序树:

 

二叉排序树的操作—查找

typedef struct
{
	KeyType key;//关键字项
	InfoType otherinfo;//其他数据域
}ElemType;

typedef struct BSTNode
{
	ElemType data;//数据域
	struct BSTNode* lchild, * rchild;//左右孩子指针
}BSTNode, * BSTree;

BSTree T;//定义二叉排序树T

二叉排序树的递归查找

BSTree SearchBST(BSTree T, KeyType key)
{
	//二叉排序树的递归查找
	if ((!T) || key == T->data.key)
		return T;
	else if (key < T->data.key)
		return SearchBST(T->lchild, key);//在左子树中继续查找
	else
		return SearchBST(T->rchild, key);//在右子树中继续查找
}

二叉排序树的查找分析

二叉排序树的查找分析

二叉排序树的操作—插入

二叉排序树的操作—生成

从空树出发,经过一系列的查找、查找操作后,可生成一棵二叉排序树。

例:设查找的关键字序列为{45,24,53,45,12,24,90},可生成二叉排序树如下:

一个无序序列可通过构造二叉树变程一个有序序列。构造树的过程就是对无序序列进行排序的过程。

插入的结点均为叶子结点,故无序移动其他结点。相当于在有序序列上插入记录而无需移动其他记录。

但是,关键字的输入顺序不同,建立的二叉排序树不同。

二叉排序树的操作—删除

从二叉排序树中删除一个结点,不能把以该结点为根的子树都删除,只能删掉该结点,并且还应保证删除后所得的二叉树仍然满足二叉排序树的性质不变

由于中序遍历二叉排序树可以得到一个递增有序的序列。那么,在二叉排序树中删去一个结点相当于删去有序序列中的一个结点。

1.将因删除结点而断开的二叉链表重新链接起来。

2.防止重新链接后树的高度增加。

总结:

3.2.平衡二叉树

平衡二叉树的定义:

又称AVL

一棵平衡二叉树或者是空树,或者是具有以下性质的二叉排序树

1.左子树与右子树的高度之差的绝对值小于等于1

2.左子树右子树也是平衡二叉排序树。

为了方便起见,给每个结点附加一个数字,给出该结点左子树与右子树的高度差。这个数字称为结点的平衡因子(BF)。

平衡因子=结点左子树的高度-结点右子树的高度

根据平衡二叉树的定义,平衡二叉树上所有结点的平衡因子只能是-101

失衡二叉排序树的分析与调整

调整前先计算哪个结点是失衡结点

如果在一棵AVL树中插入一个新结点后造成失衡,则必须重新调整树的结构,使之恢复平衡

平衡调整的四种类型:

(1)LL型调整

AVL树LL调整—例子:

(2)RR型调整

AVL树RR调整—例子:

(3)LR型调整

AVL树LR调整—例子:

(4)RL型调整

AVL树RL调整—例子:

例题:

输入关键字序列(16,3,7,11,9,26,18,14,15)给出构造一棵AVL树的步骤。

   

   

3.3.B-树

结点个数最多为阶数-1,此题为5-1=4

结点个数最少为m/2取上限-1,此题为2

添加结点都是往叶子结点上添加

B-树线是连在缝中的

用以下关键字序列{1,2,6,7,11,4,8,13,10,5,17,9,16,20,3,12,14,18,19,15}创建一棵5阶B-树

对于该B-树,给出删除关键字8、16、15、4的过程。

 

 

3.4.B+树

B+树线是连在结点上的

 


4.哈希表的查找

散列表的基本概念

基本思想:记录的存储位置域关键字之间存在对应的关系。

对应关系—hash函数

Loc(i)=H(keyi)

优点:查找效率高  

缺点:空间效率低

散列表的若干术语

散列方法(杂凑法):

选取某个函数,依该函数按关键字计算元素的存储位置,并按此存放;

查找时,由同一个函数对给定值k计算地址,将k与地址单元中元素关键码进行对比,确定查找是否成功。

散列函数(杂凑函数):

散列方法中使用的转换函数

散列表(杂凑表):

按照上述思想构造的表。

散列函数:H(key)=k

冲突:

不同的关键码映射到同一个散列地址 key1≠key2,但是H(key1)=H(key2)。

同义词:

具有相同函数值的多个关键字。

散列函数的构造方法

散列存储:

选取某个函数,依该函数按关键字计算元素的存储位置

在散列查找法中,冲突是不可能避免的,只能尽可能减少。

1.直接定址法

Hash(key) = a*key + b   (a,b为常数)

优点:以关键码key的某个线性函数值为散列地址,不会产生冲突。

缺点:要占用连续地址空间,空间效率低

例:{100,300,500,700,800,900},

散列函数 Hash(key) = key/100  (a=1/100,b=0)

2.除留余数法

Hash(key) = key mod p  (p是一个整数)

技巧:设表长为m,取p≤m且为质数

例:{15,23,27,38,53,61,70},

散列函数 Hash(key) = key mod 7

处理冲突的方法

1.开放地址法(开地址法)

基本思想:有冲突时就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将数据元素存入。

1.1线性探测法

例:关键码集为{47,7,9,11,16,92,22,8,3},散列表的表长为11;散列函数为Hash(key) = key mod 11;拟用线性探测法处理冲突。建散列表如下:

解释:

①.47、7均是由散列函数得到的没有冲突的散列地址;

②.Hash(29)=7,散列地址有冲突,需寻找下一个空的散列地址:

由H1=(Hash(29)+1) mod 11 =8,散列地址8为空,因此将29存入。

③.11、16、92均是由散列函数得到的没有冲突的散列地址;

④.22、8、3(3的di为3)的情况同②

平均查找长度ASL=(1+2+1+1+1+4+1+2+2)/9=1.67

1.2.二次探测法

关键码集为{47,7,29,11,16,92,22,8,3}

设:散列函数为Hash(key) = key mod 11

其中:m为散列表长度,m要求是某个4k+3的质数;

           di为增量序列

1.3.伪随机探测法

   (1 ≤ i < m)

其中:m为散列表长度

           di为伪随机数

2.链地址法(拉链法)

基本思想:相同散列地址的记录链成一单链表,m个散列地址就设m个单链表,然后用一个数组将m个单链表的表头指针存储起来,形成一个动态的结构。

链地址法建立散列表步骤:

Step1:取数据元素的关键字key,计算其散列函数值(地址)。若该地址对于的链表为空,则将该元素插入此链表;否则执行Step2解决冲突。

Step2:根据选择的冲突处理方法,计算关键字key的下一个存储地址。若该地址对于的链表不为空,则利用链表的前插法或后插法将该元素插入此链表。

链地址法的优点:

1.非同义词不会冲突,无“聚集”现象。

2.链表上的结点空间动态申请,更适合于表长不确定的情况。

散列表的查找

例:已知一组关键字{19,14,23,1,68,20,84,27,55,11,10,79},散列函数为:H(key) = key mod 13,散列表长为m = 16,设每个记录的查找概率相等。

(1)用线性探测再散列处理冲突,即

H(19) = 6

H(14) = 1

H(23) = 10

H(1) = 1     冲突,H(1)=(1 + 1) MOD 13 = 2

H(68) = 3

H(20) = 7

H(84) = 6     冲突,H(84)=(84 + 1) MOD 13 = 7

                    冲突,H(84)=(84 + 2) MOD 13 = 8

H(27) = 1     冲突,H(27)=(27 + 1) MOD 13 = 2

                    冲突,H(27)=(27 + 2) MOD 13 = 3

                    冲突,H(27)=(27 + 3) MOD 13 = 4

......

ASL = (1*6 + 2 + 3*3 + 4 + 9)/12 = 2.5

(2)链地址法处理冲突

对于关键字集{19,14,23,1,68,20,84,27,55,11,10,79},n=12

无序表查找ASL=(n+1)/2=6.5

有序表折半查找ASL=≈2.7

散列表上查找ASL≠O(1)

散列表的查找效率分析

使用平均查找长度ASL来衡量查找算法,ASL取决于

1.散列函数

2.处理冲突的方法

3.散列表的填装因子α=表中填入的记录数/哈希表的长度

(α越大,表中记录数越多,说明表装得越满,发生冲突的可能性就越大,查找时比较次数就越多)

ASL与填装因子α有关!既不是严格的O(1),也不是O(n)

结论

散列表技术具有很好的平均性能,优于一些传统的技术;

链地址法优于开地址法;→动态

除留余数法作散列函数优于其他类型函数。(取小于等于表长的质数,获得比较好的散列效果)


 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值