关键字(又叫"关键码"): 可以标识一个或多个记录的某个数据项(字段 )。 键值: 关键码的值。
通常一个数据元素只有一个主关键字,但可以有多个次关键字。
※ 严蔚敏的书中将"关键字"定义为: 关键字是数据元素(或记录)中某个数据项的值,用以标识(识别)一个数据元素(或记录)。 |
查找的五大算法
|
顺序查找(sequential search): 从表中最后一个记录开始,逐个进行记录的关键字和给定值的比较,若某个记录的 关键字和给定值比较相等,则查找成功,找到所查记录;反之,查找不成功。 |
//起始部分 |
#include "stdio.h" #include "stdlib.h" #include "io.h" #include "math.h" #include "time.h"
#define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 #define MAXSIZE 100 /* 存储空间初始分配量 */
typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
int F[100]; /* 斐波那契数列 */ |
/* 无哨兵顺序查找,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_Search2(int *a,int n,int key) { int i; a[0]=key; i=n; while(a[i]!=key) { i--; } return i; } |
//折半查找 (Binary search): (1) 先给数据排序(升序),形成有序表。 (2) 将key与正中元素相比,若key小,则缩小至右半部内查找;再取其中值比较,每次缩小1/2的范围,直到查找成功或失败为止。 |
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; /* 若相等则说明mid即为查找到的位置 */ }
} return 0; } |
//插值查找(Interpolation Search)/按比例查找: 根据要查找的关键字key与查找表中最大最小记录的关键字比较后的查找方法,其核心就在于插值的计算公式(key-a[low])/(a[high]-a[low])。 |
int Interpolation_Search(int *a,int n,int key) { int low,high,mid; low=1; /* 定义最低下标为记录首位 */ high=n; /* 定义最高下标为记录末位 */ while(low<=high) { mid=low+ (high-low)*(key-a[low])/(a[high]-a[low]); /* 插值 */ if (key<a[mid]) /* 若查找值比插值小 */ high=mid-1; /* 最高下标调整到插值下标小一位 */ else if (key>a[mid])/* 若查找值比插值大 */ low=mid+1; /* 最低下标调整到插值下标大一位 */ else return mid; /* 若相等则说明mid即为查找到的位置 */ } return 0; } |
//斐波那契查找 斐波那契查找就是在二分查找的基础上根据斐波那契数列进行分割的。 例如斐波那契数89,我们可以把它想象成整个有序表的元素个数,而89是由前面的两个斐波那契数34和55相加之后的和,也就是说把元素个数为89的有序表分成由前55个数据元素组成的前半段和由后34个数据元素组成的后半段,那么前半段元素个数和整个有序表长度的比值就接近黄金比值0.618. |
int Fibonacci_Search(int *a,int n,int key) { int low,high,mid,i,k=0; low=1; /* 定义最低下标为记录首位 */ high=n; /* 定义最高下标为记录末位 */ while(n>F[k]-1) 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; }
} return 0; } |
//主函数 |
int main(void) {
int a[MAXSIZE+1],i,result; int arr[MAXSIZE]={0,1,16,24,35,47,59,62,73,88,99};
for(i=0;i<=MAXSIZE;i++) { a[i]=i; } result=Sequential_Search(a,MAXSIZE,MAXSIZE); printf("Sequential_Search:%d \n",result); result=Sequential_Search2(a,MAXSIZE,1); printf("Sequential_Search2:%d \n",result);
result=Binary_Search(arr,10,62); printf("Binary_Search:%d \n",result);
result=Interpolation_Search(arr,10,62); printf("Interpolation_Search:%d \n",result);
F[0]=0; F[1]=1; for(i = 2;i < 100;i++) { F[i] = F[i-1] + F[i-2]; } result=Fibonacci_Search(arr,10,62); printf("Fibonacci_Search:%d \n",result);
return 0; } |
折半查找是进行加法与除法运算(mid=(low+high)/2); 插值查找进行复杂的四则运算(mid=low+(high-low)*(key-a[low])/(a[high]-a[low])); 而斐波那契查找只是最简单加减法运算(mid=low+F[k-1]-1); 在海量数据的查找过程中,这种细微的差别可能会影响最终的查找效率。 应该说,三种有序表的查找本质上是分隔点的选择不同,各有优劣,实际开发时可根据数据的特点综合考虑再做出选择。 |
线性索引查找 索引(n.)是为了加快查找速度而设计的一种数据结构, 索引(v)就是把一个关键字与它对应的记录相关联的过程.
所谓线性索引就是将索引项集合组织为线性结构,也称为索引表。
三种重要的线性索引:稠密索引、分块索引和倒排索引。 ① 稠密索引是指在线性索引中,将数据集中的每个记录对应一个索引项; 对于稠密索引这个索引表来说,索引项一定是按照关键码有序的排列:
② 分块有序,是把数据集的记录分成了若干块,并且这些块需要满足两个条件:块内无序; 块间有序
③ 倒排索引源于实际应用中需要根据属性的值来查找记录。这种索引表中的每一项都包括一个属性值和具有该属性值的各记录的地址。由于不是由记录来确定属性值,而是由属性值来确定记录的位置,因而称为倒排索引(inverted index)。带有倒排索引的文件我们称为倒排索引文件,简称倒排文件(inverted file)。 |
二叉排序树(Binary Sort Tree),又称为二叉查找树。它或者是一棵空树,或者是具有下列性质的二叉树。 -若它的左子树不空,则左子树上所有结点的值均小于它的根结构的值; -若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; -它的左、右子树也分别为二叉排序树。 |
//起始部分 |
#include "stdio.h" #include "stdlib.h" #include "io.h" #include "math.h" #include "time.h"
#define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 #define MAXSIZE 100 /* 存储空间初始分配量 */
typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */ |
/* 二叉树的二叉链表结点结构定义 */ |
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; /* 树中已有关键字相同的结点,不再插入 */ } |
/* 从二叉排序树中删除结点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; } |
/* 若二叉排序树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);
} } |
//主函数 |
int main(void) { int i; int a[10]={62,88,58,47,35,73,51,99,37,93}; BiTree T=NULL;
for(i=0;i<10;i++) { InsertBST(&T, a[i]); } DeleteBST(&T,93); DeleteBST(&T,47); printf("本样例建议断点跟踪查看二叉排序树结构"); return 0; }
|
平衡二叉树(Self-Balancing Binary SearchTree或Height-Balanced Binary Search Tree),是一种二叉排序树,其中每一个节点的左子树和右子树的高度差至多等于1。 ※ 我们将二叉树上结点的左子树深度减去右子树深度的值称为平衡因子BF(Balance Factor),那么平衡二叉树上所有结点的平衡因子只可能是-1、0和1。 |
//起始部分 |
#include "stdio.h" #include "stdlib.h" #include "io.h" #include "math.h" #include "time.h"
#define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 #define MAXSIZE 100 /* 存储空间初始分配量 */
typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */ |
/* 二叉树的二叉链表结点结构定义 */ |
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指向新的根结点 */ } |
/* 对以指针T所指结点为根的二叉树作左平衡旋转处理 */ /* 本算法结束时,指针T指向新的根结点 */ |
#define LH +1 /* 左高 */ #define EH 0 /* 等高 */ #define RH -1 /* 右高 */
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; } |
//主函数 |
int main(void) { int i; int a[10]={3,2,1,4,5,6,7,10,9,8}; BiTree T=NULL; Status taller; for(i=0;i<10;i++) { InsertAVL(&T,a[i],&taller); } printf("本样例建议断点跟踪查看平衡二叉树结构"); return 0; } |
多路查找树(muitl-way search tree): 其每一个结点的孩子数可以多于两个,且每一个结点处可以存储多个元素。由于它是查找树(排序树),所有元素之间存在某种特定的排序关系。 多路查找树: 可以是不平衡的, 也可以是平衡的, 如B树(包括属于B树的2-3树和2-3-4树)是平衡的.
2-3树: 是这样的一棵多路查找树:其中的每一个结点都具有两个孩子(我们称它为2结点)或三个孩子(我们称它为3结点); 一个2结点包含一个元素和两个孩子(或没有孩子),一个3结点包含一小一大两个元素和三个孩子(或没有孩子).
2-3-4树: 就是2-3树的概念扩展,包括了4结点的使用。一个4结点包含小中大三个元素和四个孩子(或没有孩子),一个4结点要么没有孩子,要么具有4个孩子。
B树(B-tree): 是一种平衡的多路查找树,2-3树和2-3-4树都是B树的特例。结点最大的孩子数目称为B树的阶(order),因此,2-3树是3阶B树,2-3-4树是4阶B树。
B+ 树: 是一种树数据结构,是一个n叉树,每个节点通常有多个孩子,一颗B+树包含根节点、内部节点和叶子节点。根节点可能是一个叶子节点,也可能是一个包含两个或两个以上孩子节点的节点。 B+ 树元素自底向上插入,这与二叉树恰好相反。 A B+ tree can be viewed as a B-tree in which each node contains only keys (not key-value pairs), and to which an additional level is added at the bottom with linked leaves. |
红黑树(RBT)的定义:它或者是一颗空树,或者是具有一下性质的二叉查找树: 1.节点非红即黑。 2.根节点是黑色。 3.所有NULL结点称为叶子节点,且认为颜色为黑。 4.所有红节点的子节点都为黑色。 5.从任一节点到其叶子节点的所有路径上都包含相同数目的黑节点。 |
//起始部分 |
#include "stdio.h" #include "stdlib.h" #include "io.h" #include "math.h" #include "time.h"
#define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0
#define MAXSIZE 100 /* 存储空间初始分配量 */
#define m 3 /* B树的阶,暂设为3 */ #define N 17 /* 数据元素个数 */ #define MAX 5 /* 字符串最大长度+1 */
typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */ |
/* B树结点和B树的类型 */ |
typedef struct BTNode { int keynum; /* 结点中关键字个数,即结点的大小 */ struct BTNode *parent; /* 指向双亲结点 */ struct Node /* 结点向量类型 */ { int key; /* 关键字向量 */ struct BTNode *ptr; /* 子树指针向量 */ int recptr; /* 记录指针向量 */ }node[m+1]; /* key,recptr的0号单元未用 */ }BTNode,*BTree; |
/* B树的查找结果类型 */ |
typedef struct { BTNode *pt; /* 指向找到的结点 */ int i; /* 1..m,在结点中的关键字序号 */ int tag; /* 1:查找成功,O:查找失败 */ }Result; |
/* 在p->node[1..keynum].key中查找i,使得p->node[i].key≤K<p->node[i+1].key */ |
int Search(BTree p, int K) { int i=0,j; for(j=1;j<=p->keynum;j++) if(p->node[j].key<=K) i=j; return i; } |
/* 在m阶B树T上查找关键字K,返回结果(pt,i,tag)。若查找成功,则特征值 */ /* tag=1,指针pt所指结点中第i个关键字等于K;否则特征值tag=0,等于K的 */ /* 关键字应插入在指针Pt所指结点中第i和第i+1个关键字之间。 */ |
Result SearchBTree(BTree T, int K) { BTree p=T,q=NULL; /* 初始化,p指向待查结点,q指向p的双亲 */ Status found=FALSE; int i=0; Result r; while(p&&!found) { i=Search(p,K); /* p->node[i].key≤K<p->node[i+1].key */ if(i>0&&p->node[i].key==K) /* 找到待查关键字 */ found=TRUE; else { q=p; p=p->node[i].ptr; } } r.i=i; if(found) /* 查找成功 */ { r.pt=p; r.tag=1; } else /* 查找不成功,返回K的插入位置信息 */ { r.pt=q; r.tag=0; } return r; } |
/* 将r->key、r和ap分别插入到q->key[i+1]、q->recptr[i+1]和q->ptr[i+1]中 */ |
void Insert(BTree *q,int i,int key,BTree ap) { int j; for(j=(*q)->keynum;j>i;j--) /* 空出(*q)->node[i+1] */ (*q)->node[j+1]=(*q)->node[j]; (*q)->node[i+1].key=key; (*q)->node[i+1].ptr=ap; (*q)->node[i+1].recptr=key; (*q)->keynum++; } |
/* 将结点q分裂成两个结点,前一半保留,后一半移入新生结点ap */ |
void split(BTree *q,BTree *ap) { int i,s=(m+1)/2; *ap=(BTree)malloc(sizeof(BTNode)); /* 生成新结点ap */ (*ap)->node[0].ptr=(*q)->node[s].ptr; /* 后一半移入ap */ for(i=s+1;i<=m;i++) { (*ap)->node[i-s]=(*q)->node[i]; if((*ap)->node[i-s].ptr) (*ap)->node[i-s].ptr->parent=*ap; } (*ap)->keynum=m-s; (*ap)->parent=(*q)->parent; (*q)->keynum=s-1; /* q的前一半保留,修改keynum */ } |
/* 生成含信息(T,r,ap)的新的根结点&T,原T和ap为子树指针 */ |
void NewRoot(BTree *T,int key,BTree ap) { BTree p; p=(BTree)malloc(sizeof(BTNode)); p->node[0].ptr=*T; *T=p; if((*T)->node[0].ptr) (*T)->node[0].ptr->parent=*T; (*T)->parent=NULL; (*T)->keynum=1; (*T)->node[1].key=key; (*T)->node[1].recptr=key; (*T)->node[1].ptr=ap; if((*T)->node[1].ptr) (*T)->node[1].ptr->parent=*T; } |
/* 在m阶B树T上结点*q的key[i]与key[i+1]之间插入关键字K的指针r。若引起 */ /* 结点过大,则沿双亲链进行必要的结点分裂调整,使T仍是m阶B树。 */ |
void InsertBTree(BTree *T,int key,BTree q,int i) { BTree ap=NULL; Status finished=FALSE; int s; int rx; rx=key; while(q&&!finished) { Insert(&q,i,rx,ap); /* 将r->key、r和ap分别插入到q->key[i+1]、q->recptr[i+1]和q->ptr[i+1]中 */ if(q->keynum<m) finished=TRUE; /* 插入完成 */ else { /* 分裂结点*q */ s=(m+1)/2; rx=q->node[s].recptr; split(&q,&ap); /* 将q->key[s+1..m],q->ptr[s..m]和q->recptr[s+1..m]移入新结点*ap */ q=q->parent; if(q) i=Search(q,key); /* 在双亲结点*q中查找rx->key的插入位置 */ } } if(!finished) /* T是空树(参数q初值为NULL)或根结点已分裂为结点*q和*ap */ NewRoot(T,rx,ap); /* 生成含信息(T,rx,ap)的新的根结点*T,原T和ap为子树指针 */ } |
/* TraverseDSTable()调用的函数 */ |
void print(BTNode c,int i) { printf("(%d)",c.node[i].key); } |
//主函数 |
int main() { int r[N]={22,16,41,58,8,11,12,16,17,22,23,31,41,52,58,59,61}; BTree T=NULL; Result s; int i; for(i=0;i<N;i++) { s=SearchBTree(T,r[i]); if(!s.tag) InsertBTree(&T,r[i],s.pt,s.i); } printf("\n请输入待查找记录的关键字: "); scanf("%d",&i); s=SearchBTree(T,i); if(s.tag) print(*(s.pt),s.i); else printf("没找到"); printf("\n");
return 0; }
|
//起始部分 |
#include "stdio.h" #include "stdlib.h" #include "io.h" #include "math.h" #include "time.h"
#define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0
#define MAXSIZE 100 /* 存储空间初始分配量 */
#define SUCCESS 1 #define UNSUCCESS 0 #define HASHSIZE 12 /* 定义散列表长为数组的长度 */ #define NULLKEY -32768
typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */ |
typedef struct { int *elem; /* 数据元素存储基址,动态分配数组 */ int count; /* 当前数据元素个数 */ }HashTable;
int m=0; /* 散列表表长,全局变量 */ |
typedef struct { int *elem; /* 数据元素存储基址,动态分配数组 */ int count; /* 当前数据元素个数 */ }HashTable; |
/* 初始化散列表 */ |
Status InitHashTable(HashTable *H) { int i; m=HASHSIZE; H->count=m; H->elem=(int *)malloc(m*sizeof(int)); for(i=0;i<m;i++) H->elem[i]=NULLKEY; return OK; } |
/* 散列函数 */ |
int Hash(int key) { return key % m; /* 除留余数法 */ } |
/* 插入关键字进散列表 */ |
void InsertHash(HashTable *H,int key) { int addr = Hash(key); /* 求散列地址 */ while (H->elem[addr] != NULLKEY) /* 如果不为空,则冲突 */ { addr = (addr+1) % m; /* 开放定址法的线性探测 */ } H->elem[addr] = key; /* 直到有空位后插入关键字 */ } |
/* 散列表查找关键字 */ |
Status SearchHash(HashTable H,int key,int *addr) { *addr = Hash(key); /* 求散列地址 */ while(H.elem[*addr] != key) /* 如果不为空,则冲突 */ { *addr = (*addr+1) % m; /* 开放定址法的线性探测 */ if (H.elem[*addr] == NULLKEY || *addr == Hash(key)) /* 如果循环回到原点 */ return UNSUCCESS; /* 则说明关键字不存在 */ } return SUCCESS; } |
//主函数 |
int main() { int arr[HASHSIZE]={12,67,56,16,25,37,22,29,15,47,48,34}; int i,p,key,result; HashTable H;
key=39;
InitHashTable(&H); for(i=0;i<m;i++) InsertHash(&H,arr[i]);
result=SearchHash(H,key,&p); if (result) printf("查找 %d 的地址为:%d \n",key,p); else printf("查找 %d 失败。\n",key);
for(i=0;i<m;i++) { key=arr[i]; SearchHash(H,key,&p); printf("查找 %d 的地址为:%d \n",key,p); }
return 0; } |
//起始部分 |
#include "string.h" #include "stdio.h" #include "stdlib.h" #include "io.h" #include "math.h" #include "time.h"
#define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0
#define MAXSIZE 40 /* 存储空间初始分配量 */
typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */ typedef int ElemType; /* ElemType类型根据实际情况而定,这里假设为int */
typedef char String[MAXSIZE+1]; /* 0号单元存放串的长度 */ |
/* 生成一个其值等于字符串常量chars的串T */ |
Status StrAssign(String T,char *chars) { int i; if(strlen(chars)>MAXSIZE) return ERROR; else { T[0]=strlen(chars); for(i=1;i<=T[0];i++) T[i]=*(chars+i-1); return OK; } } |
/* 若串S存在, 由串S复制得串T */ |
Status StrCopy(String T,String S) { int i; for(i=0;i<=S[0];i++) T[i]=S[i]; return OK; } |
/* 若S为空串,则返回TRUE,否则返回FALSE */ |
Status StrEmpty(String S) { if(S[0]==0) return TRUE; else return FALSE; } |
/* 初始条件: 串S和T存在 */ /* 操作结果: 若S>T,则返回值>0;若S=T,则返回值=0;若S<T,则返回值<0 */ |
int StrCompare(String S,String T) { int i; for(i=1;i<=S[0]&&i<=T[0];++i) if(S[i]!=T[i]) return S[i]-T[i]; return S[0]-T[0]; } |
/* 返回串的元素个数, 即串的长度 */ |
int StrLength(String S) { return S[0]; } |
/* 初始条件:串S存在。操作结果:将S清为空串 */ |
Status ClearString(String S) { S[0]=0;/* 令串长为零 */ return OK; } |
/* 用T返回S1和S2联接而成的新串。若未截断(即S1和S2的所有字符没有缺失),则返回TRUE,否则FALSE */ |
Status Concat(String T,String S1,String S2) { int i; if(S1[0]+S2[0]<=MAXSIZE) { /* 未截断 */ for(i=1;i<=S1[0];i++) T[i]=S1[i]; for(i=1;i<=S2[0];i++) T[S1[0]+i]=S2[i]; T[0]=S1[0]+S2[0]; return TRUE; } else { /* 截断S2 */ for(i=1;i<=S1[0];i++) T[i]=S1[i]; for(i=1;i<=MAXSIZE-S1[0];i++) T[S1[0]+i]=S2[i]; T[0]=MAXSIZE; return FALSE; } } |
/* 用Sub返回串S的第pos个字符起长度为len的子串。 */ |
Status SubString(String Sub,String S,int pos,int len) { int i; if(pos<1||pos>S[0]||len<0||len>S[0]-pos+1) return ERROR; for(i=1;i<=len;i++) Sub[i]=S[pos+i-1]; Sub[0]=len; return OK; } |
/* 返回子串T在主串S中第pos个字符之后的位置。若不存在,则函数返回值为0。 */ /* 其中,T非空,1≤pos≤StrLength(S)。 */ |
int Index(String S, String T, int pos) { int i = pos; /* i用于主串S中当前位置下标值,若pos不为1,则从pos位置开始匹配 */ int j = 1; /* j用于子串T中当前位置下标值 */ while (i <= S[0] && j <= T[0]) /* 若i小于S的长度并且j小于T的长度时,循环继续 */ { if (S[i] == T[j]) /* 两字母相等则继续 */ { ++i; ++j; } else /* 指针后退重新开始匹配 */ { i = i-j+2; /* i退回到上次匹配的首位的下一位 */ j = 1; /* j退回到子串T的首位 */ } } if (j > T[0]) return i-T[0]; else return 0; } |
/* T为非空串。若主串S中第pos个字符之后存在与T相等的子串, */ /* 则返回第一个这样的子串在S中的位置,否则返回0 */ |
int Index2(String S, String T, int pos) { int n,m,i; String sub; if (pos > 0) { n = StrLength(S); /* 得到主串S的长度 */ m = StrLength(T); /* 得到子串T的长度 */ i = pos; while (i <= n-m+1) { SubString (sub, S, i, m); /* 取主串中第i个位置长度与T相等的子串给sub */ if (StrCompare(sub,T) != 0) /* 如果两串不相等 */ ++i; else /* 如果两串相等 */ return i; /* 则返回i值 */ } } return 0; /* 若无子串与T相等,返回0 */ } |
/* 初始条件: 串S和T存在,1≤pos≤StrLength(S)+1 */ /* 操作结果: 在串S的第pos个字符之前插入串T。完全插入返回TRUE,部分插入返回FALSE */ |
Status StrInsert(String S,int pos,String T) { int i; if(pos<1||pos>S[0]+1) return ERROR; if(S[0]+T[0]<=MAXSIZE) { /* 完全插入 */ for(i=S[0];i>=pos;i--) S[i+T[0]]=S[i]; for(i=pos;i<pos+T[0];i++) S[i]=T[i-pos+1]; S[0]=S[0]+T[0]; return TRUE; } else { /* 部分插入 */ for(i=MAXSIZE;i<=pos;i--) S[i]=S[i-T[0]]; for(i=pos;i<pos+T[0];i++) S[i]=T[i-pos+1]; S[0]=MAXSIZE; return FALSE; } } |
/* 初始条件: 串S存在,1≤pos≤StrLength(S)-len+1 */ /* 操作结果: 从串S中删除第pos个字符起长度为len的子串 */ |
Status StrDelete(String S,int pos,int len) { int i; if(pos<1||pos>S[0]-len+1||len<0) return ERROR; for(i=pos+len;i<=S[0];i++) S[i-len]=S[i]; S[0]-=len; return OK; } |
/* 初始条件: 串S,T和V存在,T是非空串(此函数与串的存储结构无关) */ /* 操作结果: 用V替换主串S中出现的所有与T相等的不重叠的子串 */ |
Status Replace(String S,String T,String V) { int i=1; /* 从串S的第一个字符起查找串T */ if(StrEmpty(T)) /* T是空串 */ return ERROR; do { i=Index(S,T,i); /* 结果i为从上一个i之后找到的子串T的位置 */ if(i) /* 串S中存在串T */ { StrDelete(S,i,StrLength(T)); /* 删除该串T */ StrInsert(S,i,V); /* 在原串T的位置插入串V */ i+=StrLength(V); /* 在插入的串V后面继续查找串T */ } }while(i); return OK; } |
/* 输出字符串T */ |
void StrPrint(String T) { int i; for(i=1;i<=T[0];i++) printf("%c",T[i]); printf("\n"); } |
//主函数 |
int main() {
int i,j; Status k; char s; String t,s1,s2; printf("请输入串s1: ");
k=StrAssign(s1,"abcd"); if(!k) { printf("串长超过MAXSIZE(=%d)\n",MAXSIZE); exit(0); } printf("串长为%d 串空否?%d(1:是 0:否)\n",StrLength(s1),StrEmpty(s1)); StrCopy(s2,s1); printf("拷贝s1生成的串为: "); StrPrint(s2); printf("请输入串s2: ");
k=StrAssign(s2,"efghijk"); if(!k) { printf("串长超过MAXSIZE(%d)\n",MAXSIZE); exit(0); } i=StrCompare(s1,s2); if(i<0) s='<'; else if(i==0) s='='; else s='>'; printf("串s1%c串s2\n",s); k=Concat(t,s1,s2); printf("串s1联接串s2得到的串t为: "); StrPrint(t); if(k==FALSE) printf("串t有截断\n"); ClearString(s1); printf("清为空串后,串s1为: "); StrPrint(s1); printf("串长为%d 串空否?%d(1:是 0:否)\n",StrLength(s1),StrEmpty(s1)); printf("求串t的子串,请输入子串的起始位置,子串长度: ");
i=2; j=3; printf("%d,%d \n",i,j);
k=SubString(s2,t,i,j); if(k) { printf("子串s2为: "); StrPrint(s2); } printf("从串t的第pos个字符起,删除len个字符,请输入pos,len: ");
i=4; j=2; printf("%d,%d \n",i,j);
StrDelete(t,i,j); printf("删除后的串t为: "); StrPrint(t); i=StrLength(s2)/2; StrInsert(s2,i,t); printf("在串s2的第%d个字符之前插入串t后,串s2为:\n",i); StrPrint(s2); i=Index(s2,t,1); printf("s2的第%d个字母起和t第一次匹配\n",i); SubString(t,s2,1,1); printf("串t为:"); StrPrint(t); Concat(s1,t,t); printf("串s1为:"); StrPrint(s1); Replace(s2,t,s1); printf("用串s1取代串s2中和串t相同的不重叠的串后,串s2为: "); StrPrint(s2);
return 0; } |
● KMP算法
//起始部分 |
#include "string.h" #include "stdio.h" #include "stdlib.h" #include "io.h" #include "math.h" #include "time.h"
#define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 #define MAXSIZE 100 /* 存储空间初始分配量 */
typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */ typedef int ElemType; /* ElemType类型根据实际情况而定,这里假设为int */
typedef char String[MAXSIZE+1]; /* 0号单元存放串的长度 */ |
/* 生成一个其值等于字符串常量chars的串T */ |
Status StrAssign(String T,char *chars) { int i; if(strlen(chars)>MAXSIZE) return ERROR; else { T[0]=strlen(chars); for(i=1;i<=T[0];i++) T[i]=*(chars+i-1); return OK; } } |
//将串清空 |
Status ClearString(String S) { S[0]=0;/* 令串长为零 */ return OK; } |
/* 输出字符串T。 */ |
void StrPrint(String T) { int i; for(i=1;i<=T[0];i++) printf("%c",T[i]); printf("\n"); } |
/* 输出Next数组值。 */ |
void NextPrint(int next[],int length) { int i; for(i=1;i<=length;i++) printf("%d",next[i]); printf("\n"); } |
/* 返回串的元素个数 */ |
int StrLength(String S) { return S[0]; } |
/* 朴素的模式匹配法*/ |
int Index(String S, String T, int pos) { int i = pos; /* i是主串S中当前位置下标值,若pos不为1,则从第pos个位置开始匹配 */ int j = 1; /* j是子串T中当前位置下标值 */ while (i <= S[0] && j <= T[0]) /* 若i小于S的长度并且j小于T的长度时,循环继续 */ { if (S[i] == T[j]) /* 两字母相等则继续 */ { ++i; ++j; } else /* 指针后退重新开始匹配 */ { i = i-j+2; /* i退回到上次匹配的首位的下一位 */ j = 1; /* j退回到子串T的首位 */ } } if (j > T[0]) return i-T[0]; else return 0; } |
/* 通过计算返回子串T的next数组。 */ |
void get_next(String T, int *next) { int i,j; i=1; j=0; next[1]=0; while (i<T[0]) /* 此处T[0]表示串T的长度 */ { if(j==0 || T[i]== T[j]) /* T[i]表示后缀的单个字符,T[j]表示前缀的单个字符 */ { ++i; ++j; next[i] = j; } else j= next[j]; /* 若字符不相同,则j值回溯 */ } } |
//朴素模式匹配算法: 每次匹配后只移动一个字符 |
//KMP算法: ※ 设计思想: 尽量利用已经部分匹配的结果信息,尽量让i不要回溯,加快模式串的滑动速度。 ① 得到一个next[]数组, 它告诉我们:当模式串(子串)中的某个字符跟主串中的某个字符失配(即比较不等)时,模式串的指针i下一步应该跳到哪个位置, 并且从主串中刚才未匹配的字符开始进行匹配, next[]数组的长度就是模式串的长度.
i指向主串正待比较的字符的位置, j指向子串正待比较的字符的位置, 因此, 当子串中的字符x跟主串中的字符a失配时,模式串的指针i下一步应该跳到位置3, 并且从主串中刚才未匹配的字符a开始进行匹配. ② 计算next[]数组的函数:
//例:在字符串的KMP模式匹配算法中,需先求解模式串p的next函数值,其定义如下。 若模式串p为"abaabaca",则其next函数值为: 01122341 1、 j=1时, next[1]=0; 2、 j=2时, k的取值为(1,2)的开区间,所以整数k是不存在的,那就是第三种情况, next[2]=1; 3、 j=3时, k的取值为(1,3)的开区间, k从最大的开始取值,然后带入含p的式子中验证等式是否成立,不成立k取第二大的值。现在是k=2,将k导入p的式子中得, p1=p2,即"a"="b",显然不成立,舍去。 k再取值就超出范围了,所以next[3]不属于第二种情况,那就是第三种了,即next[3]=1; 4、 j=4时, k的取值为( 1, 4)的开区间,先取k=3,将k导入p的式子中得, p1p2=p2p3,不成立。 再取k=2,得p1=p3,成立。所以next[4]=2; 5、 j=5时, k的取值为( 1, 5)的开区间,先取k=4,将k导入p的式子中得, p1p2p3=p2p3p4,不成立。 再取k=3, 得p1p2=p3p4,不成立。 再取k=2,得p1=p4,成立。所以next[5]=2; 6、 j=6时, k的取值为( 1, 6)的开区间,先取k=5,将k导入p的式子中得,p1p2p3p4=p2p3p4p5,不成立。 取k=4,得p1p2p3=p3p4p5,不成立。再取k=3,将k导入p的式子中得,p1p2=p4p5,成立。所以next[6]=3; 7、 j=7时, k的取值为( 1, 7)的开区间,先取k=6,将k导入p的式子中得,p1p2p3p4p5=p2p3p4p5p6,不成立。再取k=5,得 p1p2p3p4=p3p4p5p6 ,不成立。 再取k=4,得p1p2p3=p4p5p6 ,成立。所以next[7]=4; 8、 j=8时, k的取值为( 1, 8)的开区间, 先取k=7,将k导入p的式子中得, p1p2p3p4p5p6=p2p3p4p5p6p7,不成立。 再取k=6,得p1p2p3p4p5=p3p4p5p6p7,不成立。 ...... 再取k=2,得p1=p7,不成立。 k再取值就超出范围了,所以next[8]不属于第二种情况,那就是第三种了,即next[8]=1; |
//KMP算法的优化: 为什么要优化? 例如:
在上面的例子中, ②③④⑤都是多余的步骤, 因此, 我们需要将next[]数组进行优化, 得到一个nextval[], 方法如下:
|
● KMP算法
/* 返回子串T在主串S中第pos个字符之后的位置。若不存在,则函数返回值为0。 */ /* T非空,1≤pos≤StrLength(S)。 */ |
int Index_KMP(String S, String T, int pos) { int i = pos; /* i用于主串S中当前位置下标值,若pos不为1,则从pos位置开始匹配 */ int j = 1; /* j用于子串T中当前位置下标值 */ int next[255]; /* 定义一next数组 */ get_next(T, next); /* 对串T作分析,得到next数组 */ while (i <= S[0] && j <= T[0]) /* 若i小于S的长度并且j小于T的长度时,循环继续 */ { if (j==0 || S[i] == T[j]) /* 两字母相等则继续,与朴素算法增加了j=0判断 */ { ++i; ++j; } else /* 指针后退重新开始匹配 */ j = next[j];/* j退回合适的位置,i值不变 */ } if (j > T[0]) return i-T[0]; else return 0; } |
/* 求模式串T的next函数修正值并存入数组nextval */ |
void get_nextval(String T, int *nextval) { int i,j; i=1; j=0; nextval[1]=0; while (i<T[0]) /* 此处T[0]表示串T的长度 */ { if(j==0 || T[i]== T[j]) /* T[i]表示后缀的单个字符,T[j]表示前缀的单个字符 */ { ++i; ++j; if (T[i]!=T[j]) /* 若当前字符与前缀字符不同 */ nextval[i] = j; /* 则当前的j为nextval在i位置的值 */ else nextval[i] = nextval[j]; /* 如果与前缀字符相同,则将前缀字符的 */ /* nextval值赋值给nextval在i位置的值 */ } else j= nextval[j]; /* 若字符不相同,则j值回溯 */ } } |
// KMP改良算法 |
int Index_KMP1(String S, String T, int pos) { int i = pos; /* i用于主串S中当前位置下标值,若pos不为1,则从pos位置开始匹配 */ int j = 1; /* j用于子串T中当前位置下标值 */ int next[255]; /* 定义一next数组 */ get_nextval(T, next); /* 对串T作分析,得到next数组 */ while (i <= S[0] && j <= T[0]) /* 若i小于S的长度并且j小于T的长度时,循环继续 */ { if (j==0 || S[i] == T[j]) /* 两字母相等则继续,与朴素算法增加了j=0判断 */ { ++i; ++j; } else /* 指针后退重新开始匹配 */ j = next[j];/* j退回合适的位置,i值不变 */ } if (j > T[0]) return i-T[0]; else return 0; } |
//主函数 |
int main() { int i,*p; String s1,s2;
StrAssign(s1,"abcdex"); printf("子串为: "); StrPrint(s1); i=StrLength(s1); p=(int*)malloc((i+1)*sizeof(int)); get_next(s1,p); printf("Next为: "); NextPrint(p,StrLength(s1)); printf("\n");
StrAssign(s1,"abcabx"); printf("子串为: "); StrPrint(s1); i=StrLength(s1); p=(int*)malloc((i+1)*sizeof(int)); get_next(s1,p); printf("Next为: "); NextPrint(p,StrLength(s1)); printf("\n");
StrAssign(s1,"ababaaaba"); printf("子串为: "); StrPrint(s1); i=StrLength(s1); p=(int*)malloc((i+1)*sizeof(int)); get_next(s1,p); printf("Next为: "); NextPrint(p,StrLength(s1)); printf("\n");
StrAssign(s1,"aaaaaaaab"); printf("子串为: "); StrPrint(s1); i=StrLength(s1); p=(int*)malloc((i+1)*sizeof(int)); get_next(s1,p); printf("Next为: "); NextPrint(p,StrLength(s1)); printf("\n");
StrAssign(s1,"ababaaaba"); printf(" 子串为: "); StrPrint(s1); i=StrLength(s1); p=(int*)malloc((i+1)*sizeof(int)); get_next(s1,p); printf(" Next为: "); NextPrint(p,StrLength(s1)); get_nextval(s1,p); printf("NextVal为: "); NextPrint(p,StrLength(s1)); printf("\n");
StrAssign(s1,"aaaaaaaab"); printf(" 子串为: "); StrPrint(s1); i=StrLength(s1); p=(int*)malloc((i+1)*sizeof(int)); get_next(s1,p); printf(" Next为: "); NextPrint(p,StrLength(s1)); get_nextval(s1,p); printf("NextVal为: "); NextPrint(p,StrLength(s1));
printf("\n");
StrAssign(s1,"00000000000000000000000000000000000000000000000001"); printf("主串为: "); StrPrint(s1); StrAssign(s2,"0000000001"); printf("子串为: "); StrPrint(s2); printf("\n"); printf("主串和子串在第%d个字符处首次匹配(朴素模式匹配算法)\n",Index(s1,s2,1)); printf("主串和子串在第%d个字符处首次匹配(KMP算法) \n",Index_KMP(s1,s2,1)); printf("主串和子串在第%d个字符处首次匹配(KMP改良算法) \n",Index_KMP1(s1,s2,1));
return 0; } |