每周一算法(之数据查找之一)

本文介绍了数据查找的重要性,并详细讲解了顺序查找和二分法查找。顺序查找的时间复杂度为O(n),而二分法查找在有序表中更高效。针对二分法,文章提出了一种改进的中间值计算方式,提高查找效率。最后提到了二叉排序树的概念,并预告将探讨平衡二叉树和索引查找。
摘要由CSDN通过智能技术生成

数据查找在开发过程中很长用,现有的算法也很丰富。

查找:就是根据给定的值,从查找表中确定一个其关键字与给定值相匹配的数据记录。

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!

插入删除均成功。


[预告]下次学习平衡二叉树及索引查找。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值