数据结构---查找
1、有序表查找
- 二分查找
- 二分查找的前提是线性表中的记录必须是关键码有序(通常从小到大有序),线性表必须采用顺序存储。
/*二分查找*/
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;
}
- 插值查找
- 插值查找是根据要查找的关键字与查找表中最大最小记录的关键字比较后的查找方法,其核心就在于插值的计算公式(key - a[low])/(a[high] - a[low])
- 斐波那契查找
/*斐波那契查找*/
int Fibonacci_Search(int *a, int n, int key)
{
int low, high, mid, i, k;
low = 1;
high = n;
k = 0;
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;
k = k - 1;
}
else if(key > a[mid])
{
low = mid + 1;
k = k - 2;
}
else
{
if(mid <= n)
return mid; /*若相等则说明mid即为查找到的位置*/
else
return n; /*若mid > n说明是补全数值,返回n*/
}
}
return 0;
}
2、线性索引查找
- 所谓线性索引就是将索引项集合组织为线性结构,也称为索引表。
- 稠密索引
- 稠密索引是指在线性索引中,将数据集中的每个记录对应一个索引项,对于稠密索引这个索引表来说,索引项一定是按照关键码有序的排列。
- 分块索引
- 分块有序是把数据集的记录分成了若干块,并且这些块满足块内无序,块间有序(例如第二块所有记录的关键字均大于第一块中所有记录的关键字),对于分块有序的数据集,将每块对应一个索引项,这种索引方法叫做分块索引。
- 倒排索引
- 索引表中不是由记录来确定属性值,而是由属性值来确定记录的位置,因而成为倒排索引。
3、二叉排序树
-
二叉排序树,又称二叉查找树。它或者是一棵空树,或者是具有下列性质的二叉树:
1)若它的左子树不空,则左子树上所有结点的值均小于它的根结构的值;
2)若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
3)它的左、右子树也分别为二叉排序树。
- 二叉排序树查找操作
/*二叉树的二叉链表结点结构定义*/
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)
*T = s; /*插入s为新的根结点*/
else if(key < p->data)
p->lchild = s; /*插入s为左孩子*/
else
p->rchild = s; /*插入s为右孩子*/
return TRUE;
}
else
return FALSE; /*树中已有关键字相同的结点,不再插入*/
}
- 二叉排序树删除操作
- 删除结点三种情况:
1)叶子结点;
2)仅有左或右子树的结点;
3)左右子树都有的结点。
/*若二叉排序树T中存在关键字等于key的数据元素时,则删除该数据元素结点,并返回TRUE,否则返回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);
}
}
/*从二叉排序树中删除结点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;
}
4、平衡二叉树(AVL树)
- 平衡二叉树,是一种二叉排序树,其中每一个结点 的左子树和右子树的高多差至多等于1。我们将二叉树上结点的左子树深度减去右子树深度的值称为平衡因子BF(Balance Factor)
/*二叉树的二叉链表结点结构定义*/
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中不存在和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;
}
}
}
}
}
5、多路查找树(B树)
- 多路查找树(muitl-way search tree),其每一个结点的孩子数可以多于两个,且每一个结点处可以存储多个元素。
- 2-3树
- 2-3树是这样一棵多路查找树:其中的每一个结点都具有两个孩子(我们称它为2结点)或三个孩子(我们称它为3结点)。
- 2-3-4树
- 2-3-4树其实就是2-3树的概念扩展,包括了4结点的使用,一个4结点包括小中大三个元素和四个孩子(或没有孩子)。
- B树
- B树是一种平衡的多路查找树,2-3树和2-3-4树都是B树的特例。结点最大的孩子数目称为B树的阶。
- B+树
6、散列表查找(哈希表)
- 散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key)。我们把这种对应关系f称为散列函数,又称为哈希函数。换这个思想,采用散列技术奖记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表(Hash table)。关键字对应的记录存储位置我们称为散列地址。