目录
查找概论
- 【静态查找】:数据集合稳定,不需要添加,删除元素的查找操作
- 【动态查找】:数据集合在查找的过程中需要同时添加或删除元素的查找操作
顺序表查找
顺序查找
- O(n)
- 又称线性查找
- 从第一个(或者最后一个)记录开始,逐个将记录的关键字和给定值进行比较
// a-数组,n-数组长度,key-要查找的关键字
int Sequential_Search(int *a, int n, int key)
{
int i;
for( i=1; i <= n; i++ ) // 防止越界
{
if( a[i] == key )
{
return i;
}
}
return 0;
}
优化:【哨兵】无需每次比较都检查是否越界
int Sequential_Search(int *a, int n, int key)
{
int i = n; // 循环从尾部开始
a[0] = key; // a[0]设为哨兵
while( a[i] != key )
{
i--;
}
return i; // 返回 0 失败
}
有序表查找
折半查找
- O(logn)
- 又称二分查找
- 取中间记录作比较,再选择作左半区或右半区继续比较
int Binary_Search(int *a, int n, int key){
int low,high,mid;
low = 1;
high = n;
while(low<=high){
mid = (low+high)/2;
if(key<a[mid];
high = mid-1;
else if(key>a[mid])
low = mid+1;
else
return mid;
}
return 0;
}
插值查找
- O(logn)
- 根据key与查找表中最大最小记录的关键字比较后的查找方法
- 表长较长,关键字分布较均匀,性能比折半查找好
- 极端不均匀的数据用插值查找不合适
- 插值计算公式:
- 将 mid = (low+high)/2 = low + (high - low)/2 的 1/2 优化
- 改为 (key-a[low]) / (a[high]-a[low])
int Binary_Search(int *a, int n, int key){
int low,high,mid;
low = 1;
high = n;
while(low<=high){
mid = low + (key-a[low])/(a[high]-a[low])*(high-low); // 插值与折半唯一不同点
if(key<a[mid];
high = mid-1;
else if(key>a[mid])
low = mid+1;
else
return mid;
}
return 0;
}
斐波那契查找
- O(logn)
- 利用斐波那契数组
void fibonacci(int *f)
{
int i;
f[0] = 1;
f[1] = 1;
for(i=2; i < MAXSIZE; ++i)
{
f[i] = f[i-2] + f[i-1];
}
}
int Fibonacci_search(int *a,int key,int n)
{
int low,high,mid,i,k;
low = 1;
high = n;
k = 0;
int F[MAXSIZE]; // 声明斐波那契数组
fibonacci(F); // 生成斐波那契数组
while( n > F[k]-1 ) { // 算出n位于斐波那契数组位置
++k;
}
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; // high调整
k = k - 1; // 斐波那契数组下标减一位
}
else if( key > a[mid] ){
low = mid + 1; // low调整
k = k - 2; // 斐波那契数组下标减二位
}
else{
if( mid <= n ) {
return mid;
}
else{
return n; // mid > n 说明是补全数值,返回n
}
}
}
return 0;
}
线性索引查找
稠密索引
- 在线性索引中将数据集中的每个记录对应一个索引项
- 索引项按照关键码有序排列
分块索引
- 减少索引项个数
- 将数据集进行分块,使分块有序,对每个分块建立一个索引项
- 分块有序
- 块内无序
- 块间有序:如第二个块中关键字均大于第一个块
倒排索引
- 记录号表存储具有相同次关键字的所有记录的记录号
二叉排序树
- 动态查找表
- 二叉排序数(Binary Sort Tree)又称为二叉查找树,它或者是一棵空树,或者是具有下列性质的二叉树:
- 若它的左子树不为空,则左子树上所有结点的值均小于它的根结构的值;
- 若它的右子树不为空,则右子树上所有结点的值均大于它的根结构的值;
- 它的左、右子树也分别为二叉排序树
二叉排序树存储结构
typedef struct BiTNode
{
int data;
struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;
二叉排序树查找
// 递归查找二叉排序树 T 中是否存在 key
// 指针 f 指向 T 的双亲,其初始值调用值为 NULL
// 若查找成功,则指针 p 指向该数据元素结点,并返回 TRUE
// 否则指针 p 指向查找路径上访问的最后一个结点,并返回 FALSE
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 ); // 在右子树继续查找
}
}
二叉排序树插入
// 当二叉排序树 T 中不存在关键字等于 key 的数据元素时,
// 插入 key 并返回 TRUE,否则返回 FALSE
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 ) // p 指向查找路径上访问的最后一个结点
{
*T = s; // 插入 s 为新的根结点
}
else if( key < p->data )
{
p->lchild = s; // 插入 s 为左孩子
}
else
{
p->rchild = s; // 插入 s 为右孩子
}
return TRUE;
}
else
{
return FALSE; // 树中已有关键字相同的结点,不再插入
}
}
二叉排序树删除
// 二叉排序树T中存在关键字等于key的数据元素,则删除该元素结点
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);
}
}
}
// 从二叉排序树中删除结点p,并重接它的左右子树
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的二叉排序树
AVL存储结构
typedef struct BiTNode
{
int data;
int bf; // 结点的平衡因子
struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;
右旋
void R_Rotate(BiTree *p)
{
BiTree L;
L = (*p)->lchild;
(*p)->lchild = L->rchild;
L->rchild = (*p);
*p = L;
}
左旋
void L_Rotate(BiTree *p)
{
BiTree R;
R = (*p)->rchild;
(*p)->rchild = R->lchild;
R->lchild = (*p);
*p = R;
}
左平衡旋转
#define LH 1 // 左高
#define EH 0 // 等高
#define RH -1 // 右高
void LeftBalance(BiTree *T)
{
BiTree L, Lr;
L = (*T)->lchild;
switch(L->bf)
{
case LH:
(*T)->bf = L->bf = EH;
R_Rotate(T);
break;
case RH:
Lr = L->rchild;
switch(Lr->bf)
{
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);
R_Rotate(T);
}
}
AVL主函数
Status InsertAVL(BiTree *T, int e, Status *taller)
{
if( !*T )
{
*T = (BiTree)malloc(sizeof(BiTNode));
(*T)->data = e;
(*T)->lchild = (*T)->rchild = NULL;
(*T)->bf = EH;
*taller = TRUE;
}
else
{
if(e == (*T)->data)
{
*taller = FALSE;
return FALSE;
}
if(e < (*T)->data)
{
if(!InsertAVL(&(*T)->lchild, e, taller))
{
return FALSE;
}
if(*taller)
{
switch((*T)->bf)
{
case LH:
LeftBalance(T);
*taller = FALSE;
break;
case EH:
(*T)->bf = LH;
*taller = TRUE;
break;
case RH:
(*T)->bf = EH;
*taller = FALSE;
break;
}
}
}
else
{
if(!InsertAVL(&(*T)->rchild, e, taller))
{
return FALSE;
}
if(*taller)
{
switch((*T)->bf)
{
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;
}
多路查找树(B树)
- 每个结点孩子数可多于两个,且每个结点可存储多个元素
2-3树
2-3-4树
B树
B+树
散列表查找(哈希表)
散列函数构造
直接定址法
数字分析法
平方取中法
折叠法
除留余数法
随机数法
处理散列冲突
开放定址法
再散列函数法
链地址法
公共溢出区法
算法实现
#define HASHSIZE 12
#define NULLKEY -32768
typedef struct
{
int *elem; // 数据元素的基址,动态分配数组
int count; // 当前数据元素的个数
}HashTable;
int InitHashTable(HashTable *H)
{
H->count = HASHSIZE;
H->elem = (int *)malloc(HASHSIZE * sizeof(int));
if( !H->elem )
{
return -1;
}
for( i=0; i < HASHSIZE; i++ )
{
H->elem[i] = NULLKEY;
}
return 0;
}
// 使用除留余数法
int Hash(int key)
{
return key % HASHSIZE;
}
// 插入关键字到散列表
void InsertHash(HashTable *H, int key)
{
int addr;
addr = Hash(key);
while( H->elem[addr] != NULLKEY ) // 如果不为空,则冲突出现
{
addr = (addr + 1) % HASHSIZE; // 开放定址法的线性探测
}
H->elem[addr] = key;
}
// 散列表查找关键字
int SearchHash(HashTable H, int key, int *addr)
{
*addr = Hash(key);
while( H.elem[*addr] != key )
{
*addr = (*addr + 1) % HASHSIZE;
if( H.elem[*addr] == NULLKEY || *addr == Hash(key) )
{
return -1;
}
}
return 0;
}
性能分析
- 散列函数是否均匀
- 处理冲突的方法
- 散列表装填因子