查找
概述
查找:
——在数据集(表)中找出一个特定元素(的位置)。
这一概念中涉及到以下几个相关的问题:
(1)数据表:什么样数据表?
也就是数据表的组织形式?例如:
汉语字典、英语辞典;
一个城市的电话号码簿。
高考成绩表。
查找表:
——同类型的数据元素(记录)所构成的集合。
顺序表:数据元素构成一个序列。
树表:以树结构的形式组织。
散列表:以某种函数的方式来确定元素的地址,实现数据表的组织。
索引表:为元素建立索引,以提高查找的速度。
显然,查找的方法取决于数据表的组织形式。
例如:在汉语字典和英语辞典中的查找就有明显差异。
因此,需要注意:如何确定查表结构?如何实现对给定数据表的查找?
(2)关于其中的特定元素
如何标识所需要查找的元素?
以高考成绩为例,显然是以准考证号标识的。
更一般地说,涉及到字段
字段:如第一章所述,一个元素通常包括多个字段,
——查找表中的元素也由多个字段(项)组成。
例如,高考成绩信息中,包括姓名、准考证号、各单科成绩和总分等字段。
称其中可以标识元素的字段关键字,分两种情况:
——主关键字:唯一标识一个元素的字段
例如,高考成绩信息中的“准考证号”字段。
次关键字:可能标识到多个元素的字段
例如,高考成绩信息中的“考生姓名”字段,
因为现实中有太多同名同姓的人。
(3)如何查找?
取决于查找表的组织方式。例如:
汉语字典的查找方法;
英语词典的查找方法;
某单位通信录中查找特定单位的方法等。
(4)查找成功和查找失败:
若找到指定的元素,则称为查找成功;否则称为查找失败
(5)如何评价查找方法的性能?
以查找长度来描述
查找长度:
——查找过程中所做的元素或关键字的比较次数。
涉及到:
平均查找长度ASL,
最大查找长度
失败查找长度
顺序表查找
普通查找
问题描述:
- 查找表以顺序结构来存储,
典型地是一维数组;也可能是链表,或顺序文件
本章更多地讨论是面向一维数组 - 要求在此表中找出关键字的值为x的元素的位置,
若查找成功,则返回其位置(即下标),
否则,返回一个表示元素不存在的下标(如0或-1)
按照查找表中数据的特性,以及对应的查找方法,可分为以下几种:
- 简单顺序查找----没有任何关于数据元素分布的特性
- 有序表查找------二分查找(折半查找)
- 索引表查找------数据表分块有序
问题描述:
在数组A[n]中查找元素关键字为x的元素,
若查找成功,则返回元素的下标,否则返回-1。
从前往后
int search ( elementtype A[],
keytype x)
{ int i;
for(i=0;i<n;i++)
if (A[i].key==x) return(i);
return(-1);
}
从后往前
int search ( elementtype A[],
keytype x)
{ int i;
for(i=n-1;i>=0;i--)
if (A[i].key==x) return(i);
return(-1);
}
int search ( elementtype A[],
keytype x)
{ int i;
A[0].key=x;
while (A[i].key != x) i--;
return(i);
}
- 各元素的查找长度:
1,2,3,…,n (或n-1: 设置监视哨时) - 等概率情况下的平均查找长度:
ASL=(1+2+……+n)/n=(n+1)/2 - 失败查找长度=n+1
二分查找
问题描述:
在递增有序(或递减有序,在此不妨采用递增)数组A[n]中查找元素关键字为x的元素,
若查找成功,则返回元素的下标,否则返回-1。
这里选mid-1、mid+1,不仅可以利用已查找的,还可以避免一种特殊情况:刚开始的max就是目标(如果以mid作为分界点会收缩到max-1,碰不到max)
int bin_search(elementtype A[ ],int n,
keytype x)
{ int mid,low=0,high=n-1;
//初始化查找区域
while ( low<=high)
{ mid=(low+high)/2;
if (x==A[mid].key) return mid;
else if (x<A[mid].key) high=mid-1;
else low=mid+1;
}
return -1; //返回查找失败的标志
}
递归写法
int binsearch(elementtype A[ ],
keytype x,int low,int high)
{
if(low>high) return -1;
else
{ mid=(low+high)/2;
if (A[mid].key= =x) return mid;
else if(x<A[mid].key) return
binsearch( A[ ],x,low,mid-1);
else return
binsearch( A[ ],x,mid+1,high);
}
}
其他一些结构
把数据有序地放到二叉树之中,这样查起来也很快。
左下角的两个先不做分析,因为这里只是简单提一下
块与块之间是有序的,但是块内部是无序的
索引表的查找方法:
分两步进行
- 首先要在索引表中查找以确定元素所在的块;
- 然后在所确定的块中进行查找。
其中,
块间查找的实现:
在索引表中的查找既可以采用简单顺序查找,
也可以采用二分查找;
块内查找的实现:
只能采用简单顺序查找。
索引表查找的性能分析:
介于简单顺序查找和二分查找之间
二叉排序树
定义:
二叉排序树是一棵二叉树,或者为空,或者满足以下条件:
- 若左子树不空,则左子树中所有结点的值小于根结点的值;
- 若右子树不空,则右子树中所有结点的值不小于根结点的值;
- 左右子树都为二叉排序树。
由定义可推出二叉排序树的特点:
按照左、中、右的次序遍历,
所得到的中序序列
是非降序列。
还有一点,从定义可以看出对于一组数据二叉排序树的结构不是唯一的,这就导致了他可能会往一端偏,所以就有了平衡二叉树,B树,红黑树等树去改善这个缺点。
查找
bnode *bstsearch(bnode *T,
elementtype x) {
p=T;
while ( p != NULL ) {
if ( p -> data = = x )
return p;
if ( x < p -> data )
p = p -> lchild;
else p = p -> rchild;
}
return NULL;
}
递归写法
bnode *bstsearch(bnode *T,
elementtype x) {
if ( T == NULL ) return T;
if (T -> data == x ) return T;
if (x < T -> data)
return bstsearch(T->lchild,x);
else
return bstsearch(T->rchild,x);
}
二叉排序树的构造:
从空树出发,依次插入各结点(作为叶子结点)。
二叉排序树中插入结点:
(1)若结点的值小于根结点的值,
则往左子树中插入
----通过递归调用插入算法来实现。
(2)若结点的值大于等于根结点的值,
则往右子树中插入(递归调用)。
按此方式递归调用若干次后,
可以搜索到一个空子树位置,即要插入的位置。
void insert(bnode *& T, bnode *s)//此函数只用于插入一个结点s
// 将s所指结点插入到以T为根指针的二叉排序树中
{ if ( T = = NULL ) // T 为空时
T = = s; // 新结点作为根结点
else if ( s->data < T->data )
insert( T -> lchild, s );
else insert( T -> rchild, s );
}
二叉排序树的构造算法
void creat(bnode *&T)
// 接受读入数据,从空树开始构造二叉排序树
{ T= =NULL;
cin>>x;
while (x!=结束符)
{
s=new bnode; s -> data=x;
s -> lchild=NULL; s -> rchild=NULL;
insert(T,s);
cin>>x;
}
}
平衡二叉树AVL
定义
平衡二叉树是一棵二叉排序树,或者为空,
或者满足以下条件:
1. 左右子树高度差的绝对值不大于1;
2. 左右子树都是平衡二叉树。
LL:
LR:
RL:
RR:
可以看出两两对称,还有为什么要是关注结点及其左右孩子,因为第一次出现不平横,只要他们的位置搞一下就能消去这个不平衡。
看个例题:
每加一个结点就不断往上走,遇到不平衡的结点就调整(由于是在构造的时候,所以一次就可以解决,不用再往上走了)
习题
第一题:先弄三个,搞掉一个调整方法,再看那个调整好的树的结构,选择一个合适的第四个数,又可以搞掉一个调整方法。继续这样的过程,直到凑够四个方法。
第二题:
第三题:
f(1)=1,f(2)=2;f(n)=f(n-1)+f(n-2)+1//f(k)表示k层最小平衡二叉树的结点数,解释一下f(n-1)+f(n-2)+1,加一很好理解,f(n-1)也很好理解因为f(n)要有n层,为什么选f(n-2),因为f(x)递增,平衡二叉树要求平衡因子绝对值不超过1,所以只能选f(n-2)
第四题:可以
如图
B树
定义:m阶B-树(m≥3)满足如下条件:
1) 每一个结点分支数≤m;
2) 根结点分支数≥2(要求此根结点不为叶子结点);
3) 其余分支结点的分支数≥ m/2 (向上取整) ;
4) 所有叶子结点在同一层;
5) 每一个结点的结构如下:
看个例子好懂一点
重点分析这个结点
上图有无,限定为[2,3]我看错了是向上取整。
插入
过程是这样的,先找到那个数插到哪个叶子里,如果分支数超出了限定就拿出一个(一般选择中间因为这样能增加容纳能力)放到上一层,如果上一层也超了,就继续往上走,直到不超。
删除
这里找了两次前驱
找了两次后继
散列表查找
引入
定义
对给定的关键字key,
用一个函数H(key)计算出元素地址,
由此而得散列表(Hash表,哈希表),
其中函数H(key)称为散列函数
此函数值称为散列地址。
然而,在实际应用中,会出现这样的情况:
k1≠k2,但H(k1)=H(k2) ,
称这种现象为冲突现象,k1,k2为同义词。
针对冲突——如何解决冲突呢?
找散列函数
各种奇怪的方法,反正目标只有一个,给元素整一个值,尽量减少冲突。
冲突了的话,我们就调用解决冲突的方法继续找位置,直到找到一个空位置,然后查找的话也想这样走一遍直到找到该元素。
ASL=每个元素查找时调用函数次数/元素总数。
冲突了就接在后面
查找
在散列表中查找元素的过程和构造的过程基本一致:
对给定关键字k,由散列函数H计算出该元素的地址H(k)。
若表中该位置为空,则查找失败。
否则,比较关键字,若相等,查找成功,否则根据构造表时所采用的处理方法找下一个地址,直至找到关键字等于k的元素(成功)或者找到空位置(失败)为止。
一般在用链地址法构造的表中进行查找,比在用线性探测法构造的表中进行查找,查找长度要小。