数据的查找是为了能够快速在数据库中找出自己想要的关键字,进而找出相关数据等。一般的查找方法有静态查找和动态查找,还有就是哈希表查找。
静态查找,是指仅仅查找,没有修改等行为,也可分为顺序表的查找、有序表的查找和有索引表的查找等等。顺序表的查找,顾名思义就是最普通的查找方式——依次查找,就是从第一个找到最好一个,直到找到数据为止,或者遍历后发现根本不存在这个关键字。这个方法比较简单,这里就不赘述了。有序表的查找是指表按照其关键字有一定的排列次序,比如从小到大的排列,这种情况下的查找就相对容易点了,可以利用折半查找。折半查找的核心代码如下所示:
int Binarysearch(int a[], int low, int high, int key){
if(low > high) return -1;
int mid = (low+high)/2;
if(a[mid] == key) return mid;
else if(a[mid] > key){
return Binarysearch(a,low,mid-1,key);
}else{
return Binarysearch(a,mid+1,high,key);
}
}
上述a为待查找的数组,low为折半查找最小的地方,这里为0,high为数组个数减1,key为要查找的关键字。
除了折半查找之外,还有一些折半查找的变形,如斐波那契查找,其查找的顺序是按照斐波那契数列的特征进行的。有兴趣的可以查看下,这里不多写了。
索引顺序表的查找,是指分块进行查找,是顺序查找的一种改进方法。利用这种查找方法的话,就不仅仅需要一张数据表了,还需要一张索引表,用来记录每一块的最大关键字。其中数据表是有序的,索引表也是有序的,查找的时候先查找索引表,如果key在两个关键字之间,就在后者的块中寻找是否存在关键字,完成查找。
动态查找,不仅仅能完成查找的功能了,常用二叉树来实现。其功能一般包括:查找关键字是否存在;插入一个关键字;删除一个关键字;删除整个表;构造空的查找表等等。常用的二叉树有二叉排序树、平衡二叉树、B-树和B+树等等。下面以二叉排序树为例讲解:
首先二叉树的创建,要有一个结构体用来存放每一个节点,然后依据相关数据创建二叉树,具体如下所示
typedef struct Node{
int value;
struct Node *lNode;
struct Node *rNode;
}BinaryTree;
void CreateTree(BinaryTree **root, int a[], int n){
if(n<1) return ;
*root = (BinaryTree*)malloc(sizeof(BinaryTree));
(*root)->value = a[0];
(*root)->lNode = NULL;
(*root)->rNode = NULL;
int i;
for(i=1; i<n; i++){
BinaryTree *node = (BinaryTree*)malloc(sizeof(BinaryTree));
node->value = a[i];
node->lNode = NULL;
node->rNode = NULL;
InsertNode(*root, node);
}
}
这里CreateTree中必须要用到双指针,因为我们还要修改(*root)本身的值,双指针可以起到这样的所用,使用单指针就只能修改其指向的值(单指针类似于函数中的取值传递,双指针取址传递),不能修改本身的值。
创建二叉树之后,就要依次插入数据a[]了,插入的程序如下
void InsertNode(BinaryTree *root, BinaryTree *node){
if(root->value < node->value){
if(root->rNode == NULL) root->rNode = node;
else InsertNode(root->rNode, node);
return ;
}else if(root->value > node->value){
if(root->lNode == NULL) root->lNode = node;
else InsertNode(root->lNode, node);
return ;
}else return ;
}
接下来重要的功能就是查找了,如果单纯的查找的话也很简单,只要遍历整棵树判断是否有与其key值相匹配的就行,但我们还要删除操作,删除操作比较复杂了,下面详细讨论,我们的查找程序如下所示
BinaryTree *SearchNode(BinaryTree *root, BinaryTree **parent, int key){
if(root == NULL) return NULL;
if(root->value == key){
return root;
}
else if(root->value > key){
*parent = root;
return SearchNode(root->lNode,parent,key);
}
else if(root->value < key){
*parent = root;
return SearchNode(root->rNode,parent,key);
}
return NULL;
}
如果存在该节点,就返回该节点,检验是否存在的话只用判断节点是否为NULL就行。parent是其父节点,也需要用双指针因为其指向的地址和自身的地址都是要修改的。
剩下的主要就是删除一棵树中的节点的操作了。被删除的节点可以分为4中类型:一,该节点没有子节点,此类型节点的删除最为简单,只需要将其父节点指向为NULL就行,但要知道其父节点是哪个Node,所以我们在search时添加其父节点;二,该节点只有左子节点,只需将该节点的key值改为其左子节点的值,其指向的左右子节点为其左子节点的左右子节点,然后删除其左子节点即可;三,该节点只有右子节点,同二;四,该节点既有左子节点,也有右子节点,我们采取的办法是取其左子节点lnode,寻找其左子节点的最右子节点,将其值赋给待删节点,最右子节点的父节点的右子节点指向最右子节点的左子节点,若lnode没有右子节点,就用其自身代替,总之就是选其最大值,当然我们也可以选取待删子节点其右子节点中最小的值,原理是相通的。具体实现如下
void DeleteTree(BinaryTree *root, int key){
BinaryTree *parent = NULL;
BinaryTree *deletenode = SearchNode(root,&parent,key);
if(deletenode == NULL){
printf("\nIt's not here!\n");
return ;
}
if((deletenode->lNode == NULL) && (deletenode->rNode == NULL)){
if(parent == NULL){
root = NULL;
}
else if(parent->lNode == deletenode) {
parent->lNode = NULL;
}
else if(parent->rNode == deletenode) {
parent->rNode = NULL;
}
free(deletenode);
}else if((deletenode->rNode == NULL) && (deletenode->lNode != NULL)){
BinaryTree *tmp = deletenode->lNode;
deletenode->value = tmp->value;
deletenode->rNode = tmp->rNode;
deletenode->lNode = tmp->lNode;
free(tmp);
}else if(deletenode->lNode == NULL && deletenode->rNode != NULL){
BinaryTree *tmp = deletenode->rNode;
deletenode->value = tmp->value;
deletenode->rNode = tmp->rNode;
deletenode->lNode = tmp->lNode;
free(tmp);
}else{
BinaryTree *tmp = deletenode;
BinaryTree *child = deletenode->lNode;
while(child->rNode){
tmp = child;
child = child->rNode;
}
deletenode->value = child->value;
if(tmp!=deletenode) tmp->rNode=child->lNode;
else tmp->lNode=child->lNode;
free(child);
}
}
剩下的就是删除整棵树了,比较简单,删左子节点,删自己,删右子节点如下所示
void DestoryTree(BinaryTree *root){
if(root == NULL) return ;
if(!root->lNode) DestoryTree(root->lNode);
if(!root->rNode) DestoryTree(root->rNode);
if(root->lNode == NULL && root->rNode == NULL){
free(root);
root = NULL;
}
}
具体的请看下面的链接:https://github.com/clarkzhang56/Search-method
平衡二叉树,是对二叉树的改进,所谓平衡二叉树,是指该树是平衡二叉树,其子节点所构成的树也是平衡二叉树,其左子节点的深度和右子节点的深度相差不能超过1。构建平衡二叉树需要不停的修改节点的位置,在insert的时候要保证该树满足平衡二叉树的条件。比二叉查找树复杂。B-树适用于文件系统,是一种多路搜索树,不一定是二叉的,一棵m阶的B-树或者是空树,或者满足如下的条件如下:
哈希表,不同于一般的查找方法,一般的查找方法都有通过key值大小的比较,使用哈希表不需要进行比较(这个提出类似于桶排序)。将其值和关键字通过函数f实现一一对应的关系,这样一来直接就寻找到了该key值所在的位置和其对应的所有数据。哈希表可描述如下:根据设定的哈希函数(散列函数)和处理冲突的方法将一组关键字映像到一个有限的连续的地址集上,并以关键字在地址中的“像”作为记录在表中的存储位置,这种表称为哈希表,这一影像过程称为哈希造表或散列,所得存储位置称哈希地址或散列地址。
常见的哈希函数的构造方法有直接定址法(H(key)=key或H(key)=a*key+b)、数字分析法、平方取中法、折叠法、除留余数法和随机数法。其中除留余数法用得最多,也最为简单。其可表示为: H(key) = key MOD p, p<=m, m为哈希表表长。不仅可以对关键字直接取模,也可在折叠、平方取中等运算后取模。不过p的选择很重要,由经验得知:一般情况下,可以选p为质数或不包含小于20的质因数的合数。
由于哈希表长度有限,哈希函数也不可能完美,所以冲突是在所难免的,常见的处理冲突的方法有开放定址法、再哈希法、链地址法和建立公共溢出区等方法。
开放定址法,就是当有冲突时,Hi = (H(key) + di) MOD m , i = 1,2,3...,k(k<=m-1),其中,di可以有三种取值方法:(1)di = 1,2,3,...,m-1(2)di = 1,-1,4,-4,9,-9,...k*k,-k*k,
k<=m/2(3)di是伪随机数序列。
链地址法,当一些记录产生冲突时,将关键字为同义词的记录存储在同一线性链表中。在链表中的插入位置可以在表头、表尾和中间等,以便保持此线性链表按关键字有序。