一条数据称为记录;
面向查找操作的数据结构 ,即查找基于的数据结构。
查找结构,决定查找方法
集合中元素之间不存在明显的组织规律,不便查找。
分为三种查找结构:线性表、树、散列表:
查找算法时间性能通过关键码的比较次数来度量。
关键码的比较次数与哪些因素有关呢?
平均查找长度:将查找算法进行的关键码的比较次数的数学期望值定义为平均查找长度,即:
ci取决于算法;pi与算法无关,取决于具体应用。如果pi是已知的,则平均查找长度只是问题规模的函数。
基本思想:从线性表的一端向另一端逐个将关键码与给定值进行比较,若相等,则查找成功,给出该记录在表中的位置;若整个表检测完仍未找到与给定值相等的关键码,则查找失败,给出失败信息。
例:查找k=35
int SeqSearch1(int r[ ], int n, int k)
//数组r[1] ~ r[n]存放查找集合
{
i = n;
while (i > 0 && r[i] != k)
i--;
return i;
}
改进版本:
基本思想:设置“哨兵”。哨兵就是待查值,将它放在查找方向的尽头处,免去了在查找过程中每一次比较后都要判断查找位置是否越界,从而提高查找速度
例:查找k=35
int SeqSearch2(int r[ ], int n, int k)
//数组r[1] ~ r[n]存放查找集合
{
r[0] = k; i = n;
while (r[i] != k)
i--;
return i;
}
注:
缺点:平均查找长度较大,特别是当待查找集合中元素较多时,查找效率较低。
有点:算法简单使用范围广
对表中记录的存储没有任何要求,顺序存储和链接存储均可;
对表中记录的有序性也没有要求,无论记录是否按关键码有序均可。
使用条件:
线性表中的记录必须按关键码有序;
必须采用顺序存储。
基本思想:在有序表中,取中间记录作为比较对象,若给定值与中间记录的关键码相等,则查找成功;若给定值小于中间记录的关键码,则在中间记录的左半区继续查找;若给定值大于中间记录的关键码,则在中间记录的右半区继续查找。不断重复上述过程,直到查找成功,或所查找的区域无记录,查找失败。
折半查找——非递归算法
int BinSearch1(int r[ ], int n, int k)
{ //数组r[1] ~ r[n]存放查找集合
low = 1; high = n;
while (low <= high)
{
mid = (low + high) / 2;
if (k < r[mid]) high = mid - 1;
else if (k > r[mid]) low = mid + 1;
else return mid;
}
return 0;
}
折半查找——递归算法
int BinSearch2(int r[ ], int low, int high, int k)
{ //数组r[1] ~ r[n]存放查找集合
if (low > high) return 0;
else {
mid = (low + high) / 2;
if (k < r[mid])
return BinSearch2(r, low, mid-1, k);
else if (k > r[mid])
return BinSearch2(r, mid+1, high, k);
else return mid;
}
}
折半查找性能分析:
查找成功:在表中查找任一记录的过程,即是折半查找判定树中从根结点到该记录结点的路径,和给定值的比较次数等于该记录结点在树中的层数。
查找不成功:查找失败的过程就是走了一条从根结点到外部结点的路径,和给定值进行的关键码的比较次数等于该路径上内部结点的个数。
具有n个结点的折半查找判定树的深度为: +1
二叉排序树的定义采用的是递归方法:
二叉排序树(也称二叉查找树):或者是一棵空的二叉树,或者是具有下列性质的二叉树:
⑴ 若它的左子树不空,则左子树上所有结点的值均小于根结点的值;
⑵ 若它的右子树不空,则右子树上所有结点的值均大于根结点的值;
⑶ 它的左右子树也都是二叉排序树。
注:中序遍历是指树的遍历方式,有前中后三种,后续再补充;
此次描述二叉树的查找,插入、删除等操作暂定;
注:树也可以,用可以用无穷极菜单来理解、
或者手工组装一个json树结构,例如甲方a,任务分配给b,c,b又分配给d,json结构可以看作为,三层结构,第一层1个集合,第二层两个集合,第三层三个集合;
在二叉排序树中查找给定值k的过程是:
上述过程一直持续到k被找到或者待查找的子树为空,如果待查找的子树为空,则查找失败。 二叉排序树的查找效率在于只需查找二个子树之一。
BiNode *BiSortTree::SearchBST(BiNode *root, int k)
{
if (root == NULL)
return NULL;
else if (root->data == k)
return root;
else if (k < root->data)
return SearchBST(root->lchild, k);
else return SearchBST(root->rchild, k);
}
二叉排序树的查找性能分析:
由序列{1, 2, 3, 4, 5}得到二叉排序树:ASL =(1+2+3+4+5)/ 5= 3
由序列{3, 1, 2, 5, 4}得到二叉排序树:ASL =(1+2+3+2+3)/ 5 = 2.2
二叉排序树的查找性能取决于二叉排序树的形状,在O(log2n)和O(n)之间。
前提疑问:
1.查找操作要完成什么任务?
通过:待查值k,来确定k在存储结构中的位置;
2.学过的查找技术?这些查找技术的共性?
顺序查找、折半查找、二叉排序树查找等。
这些查找技术都是通过一系列的给定值与关键码的比较,查找效率依赖于查找过程中进行的给定值与关键码的比较次数。
3.能否不用比较,通过关键码直接确定存储位置?
在存储位置和关键码之间建立一个确定的对应关系。
(有点像数据库索引的功能)
散列的基本思想:在记录的存储地址和它的关键码之间建立一个确定的对应关系。这样,不经过比较,一次读取就能得到所查元素的查找方法。
(像索引文件)
散列表:采用散列技术将记录存储在一块连续的存储空间中,这块连续的存储空间称为散列表。
散列函数:将关键码映射为散列表中适当存储位置的函数。
散列地址:由散列函数所得的存储地址 。
散列技术一般不适用于允许多个记录有同样关键码的情况。散列方法也不适用于范围查找,换言之,在散列表中,我们不可能找到最大或最小关键码的记录,也不可能找到在某一范围内的记录
散列技术最适合回答的问题是:如果有的话,哪个记录的关键码等于待查值。
注:
散列技术的关键问题:
⑴ 散列函数的设计。如何设计一个简单、均匀、存储利用率高的散列函数。
⑵ 冲突的处理。如何采取合适的处理冲突方法来解决冲突。
冲突:对于两个不同关键码ki≠kj,有H(ki)=H(kj),即两个不同的记录需要存放在同一个存储位置,ki和kj相对于H称做同义词。
⑴ 计算简单。散列函数不应该有很大的计算量,否则会降低查找效率。
⑵ 函数值即散列地址分布均匀。函数值要尽量均匀散布在地址空间,这样才能保证存储空间的有效利用并减少冲突。
散列函数是关键码的线性函数,即:
H(key) = a key + b (a,b为常数)
例:关键码集合为{10, 30, 50, 70, 80, 90},选取的散列函数为H(key)=key/10,则散列表为:
适用情况?
事先知道关键码,关键码集合不是很大且连续性较好
散列函数为:
H(key)=key mod p
如何选取合适的 p,产生较少同义词?
例: p =21=3×7
一般情况下,选p为小于或等于表长(最好接近表长)的最小素数或不包含小于20质因子的合数。
适用情况?
除留余数法是一种最简单、也是最常用的构造散列函数的方法,并且不要求事先知道关键码的分布。
根据关键码在各个位上的分布情况,选取分布比较均匀的若干位组成散列地址。
例:关键码为8位十进制数,散列地址为2位十进制数
① ② ③ ④ ⑤ ⑥ ⑦ ⑧
8 1 3 4 6 5 3 2
8 1 3 7 2 2 4 2
8 1 3 8 7 4 2 2
8 1 3 0 1 3 6 7
8 1 3 2 2 8 1 7
8 1 3 3 8 9 6 7
适用情况:
能预先估计出全部关键码的每一位上各种数字出现的频度,不同的关键码集合需要重新分析
对关键码平方后,按散列表大小,取中间的若干位作为散列地址(平方后截取)。
例:散列地址为2位,则关键码123的散列地址为:
(1234)2=1522756
适用情况:
事先不知道关键码的分布且关键码的位数不是很大
将关键码从左到右分割成位数相等的几部分,将这几部分叠加求和,取后几位作为散列地址
例:设关键码为2 5 3 4 6 3 5 8 7 0 5,散列地址为三位。
适用情况:
关键码位数很多,事先不知道关键码的分布。
由关键码得到的散列地址一旦产生了冲突,就去寻找下一个空的散列地址,并将记录存入。
如何寻找下一个空的散列地址?
(1)线性探测法 (2)二次探测法 (3)随机探测法
例:关键码集合 {47, 7, 29, 11, 16, 92, 22, 8, 3},散列函数为H(key)=key mod 11,用拉链法处理冲突,构造的开散列表为:
伪代码:
1. 计算散列地址j;
2. 在第j个同义词子表中顺序查找;
3. 若查找成功,则返回结点的地址;
否则,将待查记录插在第j个同义词子表的表头。
c++
Node *HashSearch2(Node *ht[ ], int m, int k)
{
j = H(k);
p = ht[j];
while (p != NULL && p->data != k)
p = p->next;
if (p->data == k) return p;
else {
q = new Node; q->data = k;
q->next = ht[j];
ht[j] = q;
}
}
散列查找的性能分析: