一、基本概念:
查找表、静态查找/动态查找、关键字(唯一确定一个记录的叫主关键字)、平均查找长度ASL(关键字比较次数*成功查找到第i个记录的概率)
二、线性表的查找
顺序查找
(适用于查找表没有规律的情况):从一端开始,逐一按给定值和关键字比较。
1.无监视哨
(直接按顺序查找,找到返回下标符号,失败返回-1)
int SeqSearch1(Record r[],int n,int k)
{
int i=0;
while(i<n&&r[i].key!=k)
i++;
if(i<n)
return i;
else
return -1;
}
2.有监视哨
(多定义一个单元存放要查找元素,每次循环时无需进行i<n判断是否越界的比较)
int SeqSearch2(Record r[],int n,int k)
{
int i=0;
r[n].key=k;//数组的下标到n-1,再定义一个单元r[n]存放查找元素
while(r[i].key!=k)
i++;
if(i<n)
return i;
else
return -1;
}
分析:
(1)查找成功时,ASL=(1+2+...+n)*(1/n)=(n+1)/2,在第i个元素查找相等时需要进行i次比较(i=1,2,...,n),而每个记录的查找概率相等为1/n;
(2)查找不成功时,需要进行n+1次比较。
故时间复杂度为O(n)。
优点:随机存储
缺点:当n很大时效率低
二分查找
(适用于查找表全部有序):取中间元素进行比较。
1.递归
(根据算法思想直接编写即可)
int BiSearch1(Record r[],int low,int high,int mid,int k)
{
if(low>high)
return -1;//不存在,查找失败返回-1
else{
mid=(low+high)/2
if(r[mid].key=k)
return mid;//返回位置
else{
if(r[mid].key<k)//在中点的右半区进行比较
return BiSearch1(r,mid+1,high,mid,k);
if(r[mid].key>k)//在中点的左半区进行比较
return BiSearch1(r,low,mid-1,mid,k);
}
}
}
2.非递归
(常考)
int BiSearch2(int low,int high,int mid,int k)
{
int low=0;//比较数组下标
high=n-1;
while(low<=high)//存在区间就折半查找比较
{
mid=(low+high)/2;
if(r[mid].key==k)
return mid;
else if(r[mid].key<k)
low=mid+1;
else
high=mid-1;
}
return -1;//如果不存在,查找失败返回-1
}
二叉判定树:
用一棵二叉树描述折半查找的过程,二叉树内结点数值即为查找有序表中记录的下标。树根结点即为中间结点,比较次数就是层数即树高。
分析:
例题:
1.判断:用二分查找任何元素都比用顺序查找方法好(×)
2.已知一个长度为16的顺序表,其元素按关键字有序,若采用折半查找算法查找一个不存在的元素,则比较的次数至少为()次,至多是()次。
答案是:5,6:第五层没有满可以找到空的,第五层有元素的孩子是空也可以找到为空。
3.
4.有一个有序表{1,3,9,12,32,41,45,62,75,77,82,95,100},当二分查找值为82的结点时,经( )次比较后查找成功。 答案是3次。
折半查找求ASL、比较次数等问题关键要画图出来,比较的是下标/位置,记得low,high的值会变化+1、-1之类的。
5.算法设计题:试编写折半查找算法。
6.二分查找方法适用于线性表。(√)
分块索引
(适用于部分有序情况:块间有序,块内无序)
引入索引表,记录每块key(对应指标中最大的关键字值)与索引位置(用指针指向最开始的序号)。
方法:
(1)先查索引表(可以顺序也可以二分,因为是有序的)以确定在哪块
(2)再在块内进行顺序查找到该数
计算平均长度:ASL=索引表+子表
例:设分块索引一个表,该表有300个数据元素,索引表用二分查找,有10个分块,求平均查找长度。 答案是18.4.
记得二分查找最好还是自己画图算出平均长度
三、树表的查找
(强调为动态查找,可进行增加、插入、删除操作)
二叉排序树
遵循规律:左子树所有结点值都小于根节点值,右子树所有结点值都大于根节点值。注意二叉排序树中的子树也满足这样规律,采用递归方法。数据域就是关键字key。
故用中序遍历后为有序序列则判断该为二叉排序树。
相关操作:
插入:
(1)若二叉树为空,则生生值为k的结点s,并将其作为根节点插入。
(2)若k小于根节点的值,则在根的左子树插入。
(3)若k大于根节点的值,则在根的右子树插入。
(4)若k等于根节点的值,无需插入。
void BiSortTree::Insert(BiNode *&ptr,int k)
{
if(ptr==NULL)
{
ptr=new BiNode;//根节点
ptr->key=k;
ptr->lchild=ptr->rchild=NULL;
}
else//不是根结点位置
{
if(ptr->key<k)
Insert(ptr->lchild,k);//插入到左子树
if(ptr->key>k)
Insert(ptr->rchild,k);//插入到右子树
}
}
void BiSortTree::Insert(int k)
{
Insert(root,k);
}
私有成员函数的第一个ptr必须是引用&
建立:
就是从空二叉排序树逐个插入。
int a[],int n//数组记录关键字
root=NULL;
for(i=0;i<n;i++)
Insert(root,a[i]);
例1:给定关键字序列画出建立二叉排序树过程。
一开始是一个空树,读到的第一个元素即为根节点,然后依次比较插入。
例2:试编写一个算法将线性表L中的数据建立一棵二叉排序树。
例3:编写算法从键盘读入一组整数,以9999为结束标志,将这些数据建立一棵二叉排序树。
例4:设采用二叉链表存储二叉排序树,
查找(值为k的结点):
1.递归
BiNode *BiSortTree::Search(BiNode *ptr,int k)
{
if(ptr==NULL)
return NULL;
else
if(ptr->key=k)
return ptr;
else if(ptr->key>k)
return Search(ptr->lchild,k);
else
return Search(ptr->rchild,k);
}
bool BisortTree::Search(int k)
{
return Search(root,k)!=NULL;
}
2.非递归:
BiNode*BiSortTree::Search2(BiNode *ptr,int k)
{
while(ptr!=NULL)
{if(k==ptr->key)
return ptr;
else if(k<ptr->key)
ptr=ptr->lchild;
else
ptr=ptr->rchild;
}
return NULL;
}
bool BiSortTree::Search2(int k)
{
return Search2(root,k)!=NULL;
}
查找性能分析:
查找次数小于等于树深度(即根节点高度)。
若是平衡二叉树,则时间复杂度为O(log2n)//类比思考二分查找 ;若是单支树,则时间复杂度为O(n);一般而言是O(log2n)
例:
平衡二叉树
(形态均匀的二叉排序树)遵循规律:左右子树都是平衡二叉树,左子树和右子树的高度差不超过1。
结点的平衡因子:左子树和右子树的高度差(只可能是0.-1.1)
如果不满足要求,则需要进行调整:
从下往上依次判断平衡与否,确定最小不平衡树,然后找到与根节点相邻的三个结点,拎出来对这三个结点进行调整,接着其余叶子结点按照二叉排序树规律放置即可。(详见一秒学会 平衡二叉树的调整,非标题党!不简单你打我! (考研数据结构)_哔哩哔哩_bilibili
例1:设一组关键字序列{4,5,7,2,1,3,6},试建立一棵平衡二叉树。
解:
例2:(判断)在建立平衡二叉树过程中,当插入新结点时该树失去平衡,则要进行旋转操作。(√)
例3:已知二叉树T的结点结构为:(bal存储结点的平衡因子)
left | data | right | bal |
试编写算法求树T中各结点的平衡因子。
B、B+树
例1:分别画出一个B树和B+树的例子,并指出它们之间的区别。
例2:判断:B+树中的所有非叶子结点仅起索引作用。(✓)
例3:
方法详见「六分钟速通」B树的插入与删除 简明易懂_哔哩哔哩_bilibili
四、散列表的查找
Hash查找(重点,必考)
基本概念:均匀分布,减少冲突
Hash表的构造:
直接定址、除留余数、数字分析、平方取中、折叠
选择适当的Hash函数可以减少冲突但不能避免冲突。
处理冲突的方法:(重要)
(1)开放地址法
一旦发生冲突了就去找空地址。
线性探测:
H=(Hash(key)+1)%m,m为hash表长,可以理解公式用公式计算,也可直接下一位找,因为肯定是小于m的
int SearchHash1(int hash[],int m,int k)
{
pos=k%m;
t=pos;
while(hash[pos]!=EMPTY)
{
if(hash[pos]==k)
return pos;
else
pos=(pos+1)%m;
if(pos==t)
return -1;
}
return -1;
}
}
(2)拉链法
把所有Hash地址相同的记录存储在一个单链表中,Hash表中存指向各单链表的指针。
Hash查找:先根据Hash函数算出给定值k的Hash地址,然后用Hash表位于该地址的关键记录与k比较。
Node* SearchHash2(Node *hash[],int m,int k)
{
pos=k%m;
p=hash[pos];
while(p&&p->data!=k)
p=p->next;
if(p)
return p;
else
return NULL;
}
例题: