查找算法小结

        数据的查找是为了能够快速在数据库中找出自己想要的关键字,进而找出相关数据等。一般的查找方法有静态查找动态查找,还有就是哈希表查找。

        静态查找,是指仅仅查找,没有修改等行为,也可分为顺序表的查找、有序表的查找和有索引表的查找等等。顺序表的查找,顾名思义就是最普通的查找方式——依次查找,就是从第一个找到最好一个,直到找到数据为止,或者遍历后发现根本不存在这个关键字。这个方法比较简单,这里就不赘述了。有序表的查找是指表按照其关键字有一定的排列次序,比如从小到大的排列,这种情况下的查找就相对容易点了,可以利用折半查找。折半查找的核心代码如下所示:

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-树或者是空树,或者满足如下的条件如下:

1、根结点至少有两个子女;
2、每个非根节点所包含的关键字个数 j 满足:┌m/2┐ - 1 <= j <= m - 1;
3、除根结点以外的所有结点(不包括叶子结点)的度数正好是关键字总数加1,故 内部子树个数 k 满足:┌m/2┐ <= k <= m ;
4、所有的叶子结点都位于同一层。
在B-树中,每个结点中关键字从小到大排列,并且当该结点的孩子是非叶子结点时,该k-1个关键字正好是k个孩子包含的关键字的值域的分划。
因为叶子结点不包含关键字,所以可以把叶子结点看成在树里实际上并不存在外部结点,指向这些外部结点的指针为空,叶子结点的数目正好等于树中所包含的关键字总个数加1。
B-树中的一个包含n个关键字,n+1个指针的结点的一般形式为: (n,P0,K1,P1,K2,P2,…,Kn,Pn)
其中,Ki为关键字,K1<K2<…<Kn, Pi 是指向包括Ki到Ki+1之间的关键字的子树的指针。(选自严蔚敏的《数据结构》)

        哈希表,不同于一般的查找方法,一般的查找方法都有通过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是伪随机数序列。

       链地址法,当一些记录产生冲突时,将关键字为同义词的记录存储在同一线性链表中。在链表中的插入位置可以在表头、表尾和中间等,以便保持此线性链表按关键字有序。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值