数据查找在开发过程中很长用,现有的算法也很丰富。
查找:就是根据给定的值,从查找表中确定一个其关键字与给定值相匹配的数据记录。
1. 最简单的数据算法就是顺序查找法。
顺序查找:从表中第一个记录开始, 逐个与给定值进行比较,如果某个记录与给定值相当,则查找成功;否则,查找失败。
int sequential_search(int *array,int n, int key)
{
if( NULL == array || 0 == 1)
return 0;
int i = 0;
for(i = 0;i < n; i++)
{
if(key == array[i])
return i;
}
return 0;
}
顺序查找最简单,但是当n值非常大时,其效率极其低下。
其时间复杂度为o(n).
2. 二分法查找
如果表中的记录是按照从小到大或从大到小顺序排列,则可以使用二分法(折半)查找。
二分法查找:在有序表中,取中间的值与给定值进行比较,如果匹配,则查找成功;如果中间值比给定值小,则在中间值的左半部分继续查找;如果中间值比给定值大,则在中间值右半区继续查找。不断重复上面的过程,直到查找成功, 或查找失败。
int binary_search(int *array, int n, int key)
{
if(NULL == array || 0 == n)
return 0;
int low, mid, high;
low = 0;
high = n - 1;
while(low < high){
mid = (low + high)/2;
if(array[mid] == key)
return mid;
else if(array[mid] > key){
high = mid-1;
}
else{
low = mid+1;
}
}
return 0;
}
二分法的时间复杂度为O(logn), 其效率比顺序查找法好了许多,只是前提需要对数据进行排序或数据已经是排好序的。如果对于静态数据(数据不增加或删除),可以使用该方法;如果需要对数据进行频繁的增加货删除操作,这种方法就不太适用了。
不过针对不同的应用,二分法还有改进的空间,试想如果我们知晓需要查找的数据位于块区域,如靠近最大值或最小值时,如果还是按照从中间开始查找,效率就不太好了。
原来的中间值: mid = (high + low)/2 = low + (high - low)/2
改进后的中间值:
mid = low + (key - a[low])/(a[high]-a[low]) * (high - low)
这样其效率会提升很多,不过这种应用需要提前知道表的大致结构。
3. 二叉排序树, 它是一颗空树或具有下列性质的二叉树:
1) 若它的左子树非空,则它的左子树上所在节点的值均小于他的根节点的值。
2) 若它的右子树非空,则它的右子树上所在节点的值均大于它的根节点的值。
3) 它的左右子树分别也是二叉排序树。
二叉树节点结构:
typedef struct BiTNode{
int data;
struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;
二叉排序树查找:
</pre><pre name="code" class="cpp">/*指针f指向T的双亲,初始调用为null
若查找成功,则指针p指向该数据元素节点,返回成功。
否则只指针p指向查找路径上访问的最后一个节点,返回失败*/
int bst_search(BiTree T, int key, BiTree f, BiTree *p)
{
if(!T){
*p = f;
return 0;
}
else if(key == T->data){
*p = T;
return 1;
}
else if(key < T->data){
return bst_search(T->lchild, key, T, p);
}
else{
return bst_search(T->rchild, key, T, p);
}
}
</pre>那么现在问题是如何构建二叉排序树呢?当然还是利用上面的二叉排序树了,即从T为空开始, 查找失败时申请空间插入第一个点, 算法如下:<p></p><p></p><pre code_snippet_id="1633147" snippet_file_name="blog_20160403_8_152943" name="code" class="cpp">int bst_insert(BiTree *T, int key)
{
BiTree p,s;
if(!bst_search(*T,key, NULL, &p)){
s = (BiTree)malloc(sizeof(BiTNode));
s->data = key;
s->lchild = s->rchild = NULL;
if(!p){
*T = s;
}
else if( key< p->data){
p->lchild = s;
}
else{
p->rchild = s;
}
return 1;
}
else{
return 0;
}
}
测试:
int main(void)
{
int a[8] = {11, 2 ,23 ,4 ,5, 60, 7,18};
BiTree T = NULL;
for(int i = 0; i < 8; i++){
bst_insert(&T, a[i]);
}
printf("Insert ok!\n");
int key = 15;
BiTree p ;
if(!bst_search(T, key, NULL, &p)){
printf("not find!\n");
}
else{
printf("found!\n");
}
return 0;
}
输出:
Insert ok!
not find!
二叉树排序树插入值简单,但是如果想要删除一个值,则比较复杂,特别是删除其中一个含有左右子树的节点,会导致二叉排序树重新排列。因此, 删除一个节点有三种情况:
(1)叶子节点
(2)仅有左或右子树的节点
(3)左右子树都有的节点
在此介绍一种算法:找到需要删除节点p的直接前驱或直接后继(二叉树中序遍历)s,用s来替换节点p,然后再删除节点s。
利用直接前驱的算法请参考《大话数据结构》p324.
再次我实现了直接后继的方法:
int 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)->rchild;
while(s->lchild){//找到待删除节点的直接后继
q = s;
s = s->lchild;
}
(*p)->data = s->data;//s指向待删除节点的直接后继
if(q!=*p){
q->lchild = s->rchild;//重接q的左子树
}
else{
q->rchild = s->rchild;//重接q的右子树
}
}
return 1;
}
int bst_delete(BiTree *T, int key)
{
if(!*T){
return 0;
}
else{
if( key == (*T)->data){
return delete(T);
}
else if(key < (*T)->data){
return bst_delete(&(*T)->lchild,key);
}
else{
return bst_delete(&(*T)->rchild,key);
}
}
}
测试:
int main(void)
{
int a[8] = {11, 2 ,23 ,4 ,5, 60, 7,18};
BiTree T = NULL;
for(int i = 0; i < 8; i++){
bst_insert(&T, a[i]);
}
printf("Insert ok!\n");
int key = 60;
BiTree p ;
if(!bst_search(T, key, NULL, &p)){
printf("1:not find!\n");
}
else{
printf("1:found!\n");
}
bst_delete(&T, key);
if(!bst_search(T, key, NULL, &p)){
printf("2:not find!\n");
}
else{
printf("2:found!\n");
}
return 0;
}
结果:
Insert ok!
1:found!
2:not find!
插入删除均成功。
[预告]下次学习平衡二叉树及索引查找。