一、基本概念
查找表(Search Table) 是由同一类型的数据元素(或记录)构成的集合。 关键字(Key)是数据元素中某个数据项的值。
若此关键字可以唯一地标识一一个记录,则称此关键字为主关键字(Primary Key)。
可以识别多个数据元素(或记录)的关键字,我们称为次关键字(Secondary Key)。
查找按照方式有静态查找,动态查找。
静态查找表(Static Search Table):只作查找操作的查找表
- 查询某个“特定的”数据元素是否在查找表中
- 检索某个“特定的”数据元素和各种属性
动态查找表(Dynamic Search Table):在查找过程中同时插入查找表中不存在的数据元素,或者从查找表中删除已经存在的某个数据元素
- 查找时插入数据元素
- 查找时删除数据元素
二、顺序查找
int Sequential_search(int a[], int key,int n)
{
for (int i = 0; i <= n; i++)
{
if (a[i] == key)
return i;
}
return 0;//未找到
}
代码浅显易懂,但到这里并不是完美,每次循环需要对i判断是否越界,那么需要哨兵,可以解决每次判断的需求。
顺序查找时间复杂度为O(n)
int Sequential_search(int a[], int key,int n)
{
int i=n;
a[0] = key;//哨兵
while (a[i] != key)
{
i--;
}
return i;
}
三、折半查找
折半查找(Binary Search) 技术,又称为二分查找。必须是关键码有序(通常从小到大有序),线性表必须采用顺序存储
。
在有序表中,取中间记录作为比较对象,若给定值与中间记录的关键字相等,则查找成功;若给定值小于中间记录的关键字,则在中间记录的左半区继续查找;若给定值大于中间记录的关键字,则在中间记录的右半区继续查找。不断重复。
时间复杂度O(log2n)(以2为底n的对数)
int Binary_search(int a[], int key,int n)
{
int low, mid, high;
high = n;
low = 1;
while (low <= high)
{//注意low<=high条件
mid = (high + low) / 2;
if (key < a[mid])
high = mid - 1;
else if (key > a[mid])
low = mid + 1;
else
return mid;
}
return 0;
}
四、分块查找
将表分成几块,块内无序,块间有序,先确定待查记录所在块,再在块内查找。
适用于分块有序表
算法实现
- 用数组存放待查记录,每个数据元素至少含有关
- 建立索引表,每个索引表结点含有最大关键字域和指向本块第一个结点的指针
- 在分块索引表中查找要查关键字所在的块。由于分块索引表是块间有序的因此很容易利用折半、插值等算法得到结果。
- 根据块首指针找到相应的块,并在块中顺序查找关键码。
五、二叉排序树
二叉排序树(Binary Sort Tree),又称二叉查找树(Binary Search Tree)
- 若它的左子树不空, 则左子树上所有结点的值均小于它的根结构的值
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值
- 它的左、右子树也分别为二叉排序树
- 没有键值相等的结点
查找操作
typedef struct BiTNode
{
int data;
struct BiTNode* lchild, * rchild;
}BiTNode,*BiTree;
/*递归查找是否存在key,f指向T的双亲,初始为NULL*/
/*若查找成功,则指针p指向该数据元素结点,并返回TRUE*/
/*否则指针p指向查找路径上访问的最后一个结点并返回FALSE*/
bool searchBST(BiTree T,int key,BiTree f,BiTree *p)
{
if (!T)//不成功
{
*p = f;
return false;
}
else if (T->data == key)
{//结构体指针用->访问
*p = T;
return true;
}
else if (key < T->data)
return searchBST(T->lchild, key, T, p);
else
return searchBST(T->rchild, key, T, p);
}
插入操作
/*当二叉排序树工中不存在关键字等于key的数据元素时,*/
/*插入key并返回TRUE,否则返回FALSE */
bool 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;
cout << "插入根节点" << endl;
}
else if (key < p->data)
p->lchild = s;
else
p->rchild = s;
return true;
}
return false;
}
删除操作
- 被删除的是叶子节点,其双亲节点指针域改为空
- 被删除的只有左子树或者右子树,其双亲结点的相应指针域的值改为"指向被删除结点的左子树或右子树”
- 被删除的结点既有左子树,也有右子树,以其(中序遍历)前驱替代之,然后再删除该前驱结点
bool deleteBST(BiTree* T, int key)
{
if (!*T)
return false;
else
{
if (key == (*T)->data)
return Delete(T);
else if (key < (*T)->data)
return deleteBST(&(*T)->lchild, key);
else
return deleteBST(&(*T)->rchild, key);
}
}
bool Delete(BiTree* p)//删除*p指向的点
{
BiTree s, q;
if ((*p)->rchild == NULL)//右子树为空
{
q = (*p);
*p = (*p)->lchild;
free(q);
}
else if ((*p)->lchild == NULL)
{
q = (*p);
*p = (*p)->rchild;
}
else
{
q = (*p);
s = q->lchild;
while (s->rchild)//转左然后向右,找到中序遍历前继
{
q = s;
s = s->rchild;
}
(*p)->data = s->data;
if (q != *p)//如果待删除节点的左儿子有右儿子,s始终为q的右儿子
q->rchild = s->lchild;
else//如果待删除节点的左儿子无右儿子,此时q==*p,q左儿子重接s的左儿子
q->lchild = s->lchild;
free(s);
}
}
在此中,s的值赋给p,而后s的左树连接到q右边,释放s节点。
六、平衡二叉树
平衡二叉树(Self-Balancing Binary Seatch Tree 或Height Balanced Binary SearchTree),是一种二叉排序树,其中每一个节点的左子树和右子树的高度差至多等于1
我们将二叉树上结点的左子树深度减去右子树深度的值称为平衡因子BF (Balance Factor),那么平衡二叉树上所有结点的平衡因子只可能是一1、0和1。只要二叉树上有一个结点的平衡因子的绝对值大于1,则该二叉树就是不平衡的。
距离插入结点最近的,且平衡因子的绝对值大于1的结点为根的子树,我们称为最小不平衡子树。
例如对于被破坏平衡的节点 a 来说:
插入方式 | 描述 | 旋转方式 |
---|---|---|
LL | 在a的左子树根节点的左子树上插入节点而破坏平衡 | 右旋转 |
RR | 在a的右子树根节点的右子树上插入节点而破坏平衡 | 左旋转 |
LR | 在a的左子树根节点的右子树上插入节点而破坏平衡 | 先左旋后右旋 |
RL | 在a的右子树根节点的左子树上插入节点而破坏平衡 | 先右旋后左旋 |
左右旋转处理
平衡二叉树构建的基本思想就是在构建二叉排序树的过程中,每当插入一个结点时,先检查是否因插入而破坏了树的平衡性,若是,则找出最小不平衡子树。在保持二叉排序树特性的前提下,调整最小不平衡子树中各结点之间的链接关系,进行相应的旋转,使之成为新的平衡子树。
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;/*指向左子树根节点*/
(*p)->lchild = L->rchild;/*L的右子树挂接p的左子树*/
L->rchild = (*p);
*p = L;
}
此函数代码的意思是说,当传入一个二叉排序树P,将它的左孩子结点定义为L,将L的右子树变成P的左子树,再将P改成L的右子树,最后将L替换P成为根结点。这样就完成了一-次右旋操作。
/*对以p为根的二叉排序树作左旋处理*/
void L_Lotate(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 /*右高*/
/* 对以指针T所指结点为根的二叉树作左 平衡旋转处理*/
/*本算法结束时,指针T指向新的根结点*/
void leftbalance(BiTree* T)/*传入需要调整的树,此时以确定不平衡*/
{
BiTree L, LR;
L = (*T)->lchild;
switch (L->bf)
{/*检查T的左子树平衡度*/
case LH:/*新结点在T的左孩子的左子树上,要做单右旋处理*/
(*T)->bf = L->bf = EH;
R_Rotate(T);
break;
case RH:/*新结点在T的左孩子的右子树上,要作双旋处理*/
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);/*对T的左子树作左旋平衡处理*/
R_Rotate(T);/*对T作右旋平衡处理*/
}
}
右平衡旋转处理方法类似,对称即可。
插入函数
bool insertAVL(BiTree* T, int e, bool* 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)
{/*存在和e有相同关键字的节点*/
*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;
}