- 查找:在数据集中寻找满足某种条件的数据元素的过程
- 查找表(查找结构):用于查找的数据集合称为查找表,它由同一类型的数据元素(或记录)组成
- 关键字:数据元素中唯一标识该元素的某个数据项的值,使用基于关键字的查找,查找结果应该是唯一的
对查找表的常见操作
- 查找符合条件的数据元素
静态查找表--仅关注查找速度即可 - 插入、删除某个数据元素
动态查找表--除了查找速度,也要关注插/删操作是否方便实现
查找算法的评价指标
- 查找长度--在查找运算中,需要对比关键字的次数
- 平均查找长度(ASL)--所有查找过程中进行关键字的比较次数的平均值
- ASL的数量级反映了查找算法时间复杂度
- 评价一个查找算法的效率时,通常考虑查找成功/查找失败两种情况的ASL
顺序查找(线性查找)
/*
通常用于线性表--顺序--链表
从头到脚挨个查找
时间复杂度O(n)
顺序查找的优化(对有序表)
*/
typedef struct{
ElemType *elem;
int TableLen;
}SSTable;
int Search_Seq(SSTable ST,ElemType key){
//ST.elem[0] = key; //0号位置存哨兵
int i;
//for(i=ST.TableLen;ST.elem[i] != key;--i);//从后往前找
for(i=0;i<ST.TableLen && ST.elem[i] != key;++i);
//return i;//查找成功,则返回元素下标;查找失败,则返回0
return i == ST.TableLen?-1:i;
/*
int Search_Seq(SSTable ST,ElemType key){
ST.elem[0] = key; //0号位置存哨兵
int i;
for(i=ST.TableLen;ST.elem[i] != key;--i);//从后往前找
return i;//查找成功,则返回元素下标;查找失败,则返回0
}
*/
}
/*
用查找判定树分析ASL
一个成功结点的查找长度 = 自身所在层数
一个失败结点的查找长度 = 其父结点所在层数
优化(被查概率不相等)
被查概率大的放在靠前位置
但当查找失败时还是需要全部对比
*/
折半查找(二分查找)
- 仅适用于有序的顺序表
- low和high分别指向目前要搜索的范围,mid为low和high的中间,搜索数大于mid,则low变为mid,小于mid,则high变为mid
- low = 0;high = TableLen-1
- min = (low + high)/2
- 折半查找的判定树一定是平衡二叉树
- 只有最下面一层是不满的,树高h=log₂(n+1)
- 判定树结点关键字:左<中<右,满足二叉排序树的定义
- 失败结点:n+1个(等于成功结点的空链域数量) 查找成功的ASL≤h,折半查找的时间复杂度O(log₂n)
//基于升序排列的折半查找
typedef struet{
ElemType *elem;
int TableLen;
}SSTable;
int Binary_Search(SSTable L,ElemType key){
int low = 0;
int high = L.TableLen-1;
int mid;
while(low < high){
mid = (low + high)/2; //取中间位置
if(L.elem[mid] == key)
return mid; //查找成功则返回所在位置
else if(L.elem[mid] > key)
high = mid - 1; //从前半部分继续查找
else
low = mid + 1; //从后半部分继续查找
}
return -1; //查找失败,返回-1
}
/*
如果当前low和high有奇数个元素,则mid分隔后,左右两部分元素个数相等
如果当前low和high有偶数个元素,则mid分隔后,左半部分比右半部分少一个元素
折半查找的判定树中,若mid = (low + high)/2,则对于任何一个结点,必有
右子树结点数 - 左子树结点数= 0或1
*/
分块查找
- 特点:块内无序,块间有序
- "索引表"中保存每个分块的最大关键字和分块的存储区就
//索引表
typedef struct{
ElemType maxValue;
int low,high;
}Index;
//顺序表存储实际元素
ElemType List[100];
/*
又称索引顺序查宅,算法过程如下:
1、在索引表中确定待查记录所属的分块(可顺序、可折半)
2、在块内顺序查找
若索引表中不包含目标关键字,则折半查找索引表最终停在low>high,要在low所指分块中查找
low超出索引表范围,查找失败
设索引查找和块内查找的平均查找长度为L1,Ls,则分块查找的平均查找长度为:
用顺序查找查索引表,则L1=(b+1)/2,Ls=(s+1)/2
则ASL=(b+1)/2 + (s+1)/2 = (s²+2s+n)/2s,求最小值min
*/
二叉排序树
- 二叉查找树(BST)
- 可用于元素的有序组织、搜索
- 进行中序遍历,可以得到一个递增的有序序列
//二叉排序树结点
typedef struct{
int key;
struct BSTNode *lchild,*rchild;
}BSTNode,*BSTree;
//在二叉排序树中查找值为key的结点,非递归的实现--最坏空间复杂度O(1)
BSTNode *BST_Search(BSTree T,int key){
while(T!=NULL && key!=T->key){ //若树空或等于根结点值,则结束循环
if(key<T->key) //小于,则在左子树上查找
T = T->rchild;
else //大于,则在右子树上查找
T = T->rchild;
}
return T;
}
//递归实现--最坏空间复杂度O(h)
BSTNode *BST_Search(BSTree T,int key){
if(T == NULL)
return NULL;//查找失败
if(key == T->key)
return T;//查找成功
else if(key < T->key)
return BSTSearch(T->lchild);//在左子树中找
else
return BSTSearch(T->rchild,key);//在右子树中找
}
//插入:若原二叉排序树为空,则直接插入结点。若非空,则比大小.
//插入关键字为k的新结点(递归实现)
int BST_Insert(BSTree &T,int k){
if(T == NULL){ //原树为空,新插入的结点为根结点
T = (BSTree)malloc(sizeof(BSTNode));
T->key = k;
T->lchild = T->rchild = NULL;
return 1; //返回1,插入成功
}
else if(k == T->key) //树中存在相同关键字的结点,插入失败
return 0;
else if(k<T->key) //插入到T的左子树
return BST_Insert(T->lchild,k);
else //插入到T的右子树
return BST_Insert(T->rchild,k);
}
//按照str[]中的关键字序列建立二叉排序树
void Creat_BST(BSTree &T,int str[],int n){
T = NULL; //初始时T为空树
int i = 0;
while(i<n){//依次将每个关键字插入到二叉排序树中
BST_Insert(T,str[]);
i++;
}
}
//不同的关键字序列可能得到同款二叉排序树,也可能不同
/*
删除
1、若被删除结点z是叶结点,则直接删除,不会破坏二叉排序树的性质
2、若结点z只有一颗左子树或右子树,则让z的子树称为z父结点的子树,替代z的位置
3、若结点z有左右两棵子树,则令z的直接后继(前驱)替代z,然后从二叉排序树中删去这一个直接后继(前驱),转换成第1或第2种情况
z的后继:z的右子树中最左下结点(该结点一定没有左子树)
z的后继:z的左子树中最右下结点(该结点一定没有右子树)
*/
平衡二叉树
平衡二叉树的插入操作:
- 插入新结点后,要保持二叉排序树的特性不变(左<中<右)
- 若插入新结点导致不平衡,则需要调整平衡 平衡二叉树的删除操作:
- 删除结点后,要保持二叉排序树的特性不变(左<中<右)
- 若删除结点导致不平衡,则需要调整平衡
/*
删除操作具体步骤:
1、删除结点
- 若删除的结点是叶子,直接删
- 若删除的结点只有一个子树,用子树顶替删除位置
- 若删除的结点有两棵子树,用前驱(或后继)结点顶替,并转换为对前驱(或后继)结点的删除
2、一路向北找到最小不平衡子树,找不到就结束
3、找最小不平衡子树下,"个头"最高的儿子、孙子
4、根据孙子的位置,调整平衡(LL/RR/LR/RL)
- 孙子在LL:儿子右单旋
- 孙子在RR:儿子左单旋
- 孙子在LR:孙子先左旋,再右旋
- 孙子在RL:孙子先右旋,再左旋
5、如果不平衡向上传导,继续2
对最小不平衡子树的旋转可能导致树变矮,从而导致上层祖先不平衡
*/
红黑树RBT
插入/删除很多时候不会破坏"红黑"特性,无需频繁调整树的形态,即便需要调整,一般都可在常数级时间内完成
- 首先是二叉排序树--->左子树结点值<根结点值<右子树根结点值
- 与普通BST相比
- 每个结点或是红色,或是黑色的
- 根结点是黑色的
- 叶结点(外部结点、NULL结点、失败结点)均是黑色的
- 不存在两个相邻的红结点(即红结点的父结点和孩子结点均是黑色)
- 对每个结点,从该结点到任一叶结点的简单路径上,所含黑结点的数目相同
- 左根右,根叶黑,不红红,黑路同
- 结点的黑高bh -- 从某结点出发(不含该结点)到达任一空叶结点的路径上黑结点总数
- 性质
- 1、从根结点到叶结点的最长路径不大于最短路径的2倍
- 2、有n个内部结点的红黑树高度h≤2log₂(n+1)--->红黑树查找操作时间复杂度O(log₂n)
//红黑树的
typedef RBnode{
int key; //关键字的值
RBnode *parent; //父结点指针
RBnode *lChild; //左孩子指针
RBnode *rChild; //右孩子指针
int color; //红黑
}