查找概论
静态查找表:只做查找操作。
动态查找表:查找时插入/删除数据元素。
顺序表查找
最基础查找算法
for循环1到n。
优化
设置“哨兵”,令a[0]=key(哨兵),从i=n开始,i–直到找到或者i=0。此方法免去了查找过程中每一次判断是否i>n越界。
时间复杂度O(n)。
有序表查找
折半查找(二分查找)
前提是线性表有序,取中间记录为比较对象,若给定值与中间记录的关键字相等,查找成功;若小于/大于,在中间记录的左/右半区重复上述操作。
使用low=1,high=n,mid=(low+high)/2。while(low<=high),若小于,high=mid-1;若大于,low=mid+1。
时间复杂度O(logn)。
插值查找
折半查找的mid=(low+high)/2,变化得到mid=low+ 1 2 \frac{1}{2} 21(high-low)。将1/2替换为 k e y − a [ l o w ] a [ h i g h ] − a [ l o w ] \frac{key-a[low]}{a[high]-a[low]} a[high]−a[low]key−a[low] ,称为插值计算公式。
时间复杂度O(logn)。
斐波那契查找
int Fibonacci_Search(int a[],int n,int key){
//a数组为目标数组,n为a数组的长度,key为要在目标数组中具体查找的数据
int low,hign,mid,i,k;
k=0;
//下面的F数组即斐波那契数列
//0 1 1 2 3 5 8 13 21 34 ...
while(n>F[k]-1){
k++; //计算n位于斐波那契数列的位置
}
for(i=n;i<F[k]-1;i++){
a[i] = a[n]; //将不满的数值补全
}
while(low<=high){
mid = low + F[k-1]-1; //计算当前分隔的下标
if(key<a[mid]){
//查找记录小于当前分隔记录
high = mid-1; //调整最高下标
k-=1; //斐波那契数列下标减一位
}
else if(key>a[mid]){
//与上同理
low = mid+1;
k-=2;
}
else{
if(mid<=n){
return mid;//若相当且mid小于n,则直接返回mid
}
else
return n;//若mid大于n,则说明是补全数值,返回n
}
}
return 0;
}
先计算n位于斐波那契数列的位置k,然后将不满的数列补全。当low<=high,mid=low+F[k-1]-1。若key<a[mid],high=mid-1,k=k-1;若key>a[mid],low=mid+1,k=k-2;若相等,若mid<=n,mid就是查找到的位置;若mid>n,n就是。
时间复杂度O(logn)。
线性索引查找
稠密索引
在线性索引中,将数据集中的每个记录对应一个索引项,索引项按照关键码有序的排列,这样在查找关键字时,可用前面的有序查找算法。
分块索引
分块有序,把数据集的记录分成了若干块,且满足块内无序、块间有序。
索引项三个数据项:
最大关键码、块个数、指向块首元素指针。
时间复杂度 最佳时(即块数与块内数相等)O( n \sqrt{n} n)。
倒排索引
索引表:次关键码和记录号表。
记录号表存储具有相同次关键字的所有记录的记录号。
二叉排序树
查找
Status SearchBST(BiTree T, int key, BiTree f, BiTree *p)
{
if (!T) /* 查找不成功 */
{
*p = f;
return FALSE;
}
else if (key==T->data) /* 查找成功 */
{
*p = T;
return TRUE;
}
else if (key<T->data)
return SearchBST(T->lchild, key, T, p); /* 在左子树中继续查找 */
else
return SearchBST(T->rchild, key, T, p); /* 在右子树中继续查找 */}
插入
Status InsertBST(BiTree *T, int key)
{
BiTree p,s;
if (!SearchBST(*T, key, NULL, &p)) /* 查找不成功 */
{
s = (BiTree)malloc(sizeof(BiTNode));
s->data = key;
s->lchild = s->rchild = NULL;
if (!p)
*T = s; /* 插入s为新的根结点 */
else if (key<p->data)
p->lchild = s; /* 插入s为左孩子 */
else
p->rchild = s; /* 插入s为右孩子 */
return TRUE;
}
else
return FALSE; /* 树中已有关键字同样的结点。不再插入 */
}
删除
Status DeleteBST(BiTree *T,int key)
{
if(!*T) /* 不存在关键字等于key的数据元素 */
return FALSE;
else
{
if (key==(*T)->data) /* 找到关键字等于key的数据元素 */
return Delete(T);
else if (key<(*T)->data)
return DeleteBST(&(*T)->lchild,key);
else
return DeleteBST(&(*T)->rchild,key);
}
}
Status Delete(BiTree *p)
{
BiTree q,s;
if((*p)->rchild==NULL) /* 右子树空则仅仅需重接它的左子树(待删结点是叶子也走此分支) */
{
q=*p; *p=(*p)->lchild; free(q);
}
else if((*p)->lchild==NULL) /* 仅仅需重接它的右子树 */
{
q=*p; *p=(*p)->rchild; free(q);
}
else /* 左右子树均不空 */
{
q=*p; s=(*p)->lchild;
while(s->rchild) /* 转左,然后向右到尽头(找待删结点的前驱) */
{
q=s;
s=s->rchild;
}
(*p)->data=s->data; /* s指向被删结点的直接前驱(将被删结点前驱的值代替被删结点的值) */
if(q!=*p)
q->rchild=s->lchild; /* 重接q的右子树 */
else
q->lchild=s->lchild; /* 重接q的左子树 */
free(s);
}
return TRUE;
}
平衡二叉树(AVL树)
高度平衡的二叉排序树。左右子树都是平衡二叉树,深度差不超过1。
距离插入结点最近的,且平衡因子的绝对值大于1的结点为根的子树,称为最小不平衡子树。
实现原理
构建二叉排序树时,每插入一个结点,检查是否因为插入破坏了平衡性,若是,找出最小不平衡子树;调整最小不平衡子树中各结点之间链接关系。
实现算法
typedef struct BiTNode /* 结点结构 */
{
int data; /* 结点数据 */
int bf; /* 结点的平衡因子 */
struct BiTNode *lchild, *rchild; /* 左右孩子指针 */
} BiTNode, *BiTree;
/* 对以p为根的二叉排序树作右旋处理。 */
/* 处理之后p指向新的树根结点。即旋转处理之前的左子树的根结点 */
void R_Rotate(BiTree *P)
{
BiTree L;
L=(*P)->lchild; /* L指向P的左子树根结点 */
(*P)->lchild=L->rchild; /* L的右子树挂接为P的左子树 */
L->rchild=(*P);
*P=L; /* P指向新的根结点 */
}
/* 对以P为根的二叉排序树作左旋处理, */
/* 处理之后P指向新的树根结点。即旋转处理之前的右子树的根结点0 */
void L_Rotate(BiTree *P)
{
BiTree R;
R=(*P)->rchild; /* R指向P的右子树根结点 */
(*P)->rchild=R->lchild; /* R的左子树挂接为P的右子树 */
R->lchild=(*P);
*P=R; /* P指向新的根结点 */
}
#define LH +1 /* 左高 */
#define EH 0 /* 等高 */
#define RH -1 /* 右高 */
/* 对以指针T所指结点为根的二叉树作左平衡旋转处理 */
/* 本算法结束时,指针T指向新的根结点 */
void LeftBalance(BiTree *T)
{
BiTree L,Lr;
L=(*T)->lchild; /* L指向T的左子树根结点 */
switch(L->bf)
{
/* 检查T的左子树的平衡度,并作对应平衡处理 */
case LH: /* 新结点插入在T的左孩子的左子树上,要作单右旋处理 */
(*T)->bf=L->bf=EH;
R_Rotate(T);
break;
case RH: /* 新结点插入在T的左孩子的右子树上。要作双旋处理 */
Lr=L->rchild; /* Lr指向T的左孩子的右子树根 */
switch(Lr->bf)
{
/* 改动T及其左孩子的平衡因子 */
case LH: (*T)->bf=RH;
L->bf=EH;
break;
case EH: (*T)->bf=L->bf=EH;
break;
case RH: (*T)->bf=EH;
L->bf=LH;
break;
}
Lr->bf=EH;
L_Rotate(&(*T)->lchild); /* 对T的左子树作左旋平衡处理 */
R_Rotate(T); /* 对T作右旋平衡处理 */
}
}
/* 对以指针T所指结点为根的二叉树作右平衡旋转处理, */
/* 本算法结束时,指针T指向新的根结点 */
void RightBalance(BiTree *T)
{
BiTree R,Rl;
R=(*T)->rchild; /* R指向T的右子树根结点 */
switch(R->bf)
{
/* 检查T的右子树的平衡度。并作对应平衡处理 */
case RH: /* 新结点插入在T的右孩子的右子树上。要作单左旋处理 */
(*T)->bf=R->bf=EH;
L_Rotate(T);
break;
case LH: /* 新结点插入在T的右孩子的左子树上,要作双旋处理 */
Rl=R->lchild; /* Rl指向T的右孩子的左子树根 */
switch(Rl->bf)
{
/* 改动T及其右孩子的平衡因子 */
case RH: (*T)->bf=LH;
R->bf=EH;
break;
case EH: (*T)->bf=R->bf=EH;
break;
case LH: (*T)->bf=EH;
R->bf=RH;
break;
}
Rl->bf=EH;
R_Rotate(&(*T)->rchild); /* 对T的右子树作右旋平衡处理 */
L_Rotate(T); /* 对T作左旋平衡处理 */
}
}
/* 若在平衡的二叉排序树T中不存在和e有同样关键字的结点,则插入一个 */
/* 数据元素为e的新结点。并返回1,否则返回0。若因插入而使二叉排序树 */
/* 失去平衡,则作平衡旋转处理。布尔变量taller反映T长高与否。 */
Status InsertAVL(BiTree *T,int e,Status *taller)
{
if(!*T)
{
/* 插入新结点。树“长高”,置taller为TRUE */
*T=(BiTree)malloc(sizeof(BiTNode));
(*T)->data=e; (*T)->lchild=(*T)->rchild=NULL; (*T)->bf=EH;
*taller=TRUE;
}
else
{
if (e==(*T)->data)
{
/* 树中已存在和e有同样关键字的结点则不再插入 */
*taller=FALSE; return FALSE;
}
if (e<(*T)->data)
{
/* 应继续在T的左子树中进行搜索 */
if(!InsertAVL(&(*T)->lchild,e,taller)) /* 未插入 */
return FALSE;
if(taller) /* 已插入到T的左子树中且左子树“长高” */
switch((*T)->bf) /* 检查T的平衡度 */
{
case LH: /* 原本左子树比右子树高。须要作左平衡处理 */
LeftBalance(T); *taller=FALSE; break;
case EH: /* 原本左、右子树等高,现因左子树增高而使树增高 */
(*T)->bf=LH; *taller=TRUE; break;
case RH: /* 原本右子树比左子树高,现左、右子树等高 */
(*T)->bf=EH; *taller=FALSE; break;
}
}
else
{
/* 应继续在T的右子树中进行搜索 */
if(!InsertAVL(&(*T)->rchild,e,taller)) /* 未插入 */
return FALSE;
if(*taller) /* 已插入到T的右子树且右子树“长高” */
switch((*T)->bf) /* 检查T的平衡度 */
{
case LH: /* 原本左子树比右子树高。现左、右子树等高 */
(*T)->bf=EH; *taller=FALSE; break;
case EH: /* 原本左、右子树等高,现因右子树增高而使树增高 */
(*T)->bf=RH; *taller=TRUE; break;
case RH: /* 原本右子树比左子树高,须要作右平衡处理 */
RightBalance(T); *taller=FALSE; break;
}
}
}
return TRUE;
}
多路查找树
每个结点的孩子数可多于2个,且每个结点处可存储多个元素。
2-3树、2-3-4树、B树、B+树
散列表查找(哈希表)
散列函数构造:直接定址法(线性函数)、数字分析法、平方取中法、折叠法、除留余数法、随机数法。
处理散列冲突:开放定址法、再散列函数法、链地址法、公共溢出区法。