数据结构与算法之查找算法

数据结构与算法——查找算法

本文将不断更新查找有关算法,由于精力有限,因此本博文将分多次更新,感谢您的关注

1. 二分法查找(折半查找)

1.1 算法叙述

  • 二分法查找又称折半查找,顾名思义也就是将待查找范围不断折半直到不能再折为止

  • 适用范围:数据表是顺序结构,也就是从小到大或从大到小已经排好序

  • 算法复杂度:O(logn)

  • 思路如下图:
    在这里插入图片描述

1.2 实例说明

  • 以NTC温度转化为例,通过ADC采集到NTC的电压之后,计算出NTC的电阻值,之后采用查表的方案查询此NTC阻值所对应的温度

  • 本文已 TSM1A682J3952RZ 这颗NTC电阻为例,数据手册地址:NTC数据手册地址

  • 代码如下:

    • temperature 内记录了从数据手册内获取到的NTC从 -40 ~ 125℃ 所对应的阻值
    • search_dichotomy 为二分法查找算法实现
#include <stdio.h>

/* ntc -40 ~ 125℃ 阻值 */
const float temperature[] = {
    249.76	,	233.36	,	218.16	,	204.07	,	190.98	,	178.8	,	167.47	,	156.9	,	147.06	,	137.88,
    129.31	,	121.31	,	113.85	,	106.88	,	100.38	,	94.3	,	88.626	,	83.325	,	78.372	,	73.743,
    69.417	,	65.372	,	61.589	,	58.05	,	54.738	,	51.638	,	48.735	,	46.015	,	43.466	,	41.075,
    38.833	,	36.729	,	34.753	,	32.897	,	31.153	,	29.513	,	27.97	,	26.518	,	25.151	,	23.863,
    22.649	,	21.505	,	20.426	,	19.407	,	18.445	,	17.537	,	16.679	,	15.868	,	15.101	,	14.375,
    13.688	,	13.039	,	12.423	,	11.84	,	11.288	,	10.765	,	10.269	,	9.7981	,	9.3517	,	8.9281,
    8.5261	,	8.1445	,	7.7821	,	7.4379	,	7.1108	,	6.8	    ,	6.5046	,	6.2237	,	5.9565	,	5.7024,
    5.4606	,	5.2305	,	5.0115	,	4.8029	,	4.6043	,	4.4151	,	4.2348	,	4.063	,	3.8992	,	3.743,
    3.594	,	3.4519	,	3.3163	,	3.1869	,	3.0633	,	2.9453	,	2.8326	,	2.7249	,	2.6219	,	2.5235,
    2.4294	,	2.3394	,	2.2533	,	2.1709	,	2.0921	,	2.0166	,	1.9442	,	1.8749	,	1.8086	,	1.7449,
    1.6839	,	1.6254	,	1.5693	,	1.5154	,	1.4637	,	1.4141	,	1.3664	,	1.3207	,	1.2767	,	1.2344,
    1.1938	,	1.1548	,	1.1172	,	1.0811	,	1.0463	,	1.0128	,	0.98063	,	0.94961	,	0.91974	,	0.89097,
    0.86325	,	0.83654	,	0.81079	,	0.78596	,	0.76202	,	0.73894	,	0.71667	,	0.69518	,	0.67444	,	0.65443,
    0.63512	,	0.61647	,	0.59846	,	0.58107	,	0.56427	,	0.54804	,	0.53237	,	0.51722	,	0.50258	,	0.48843,
    0.47475	,	0.46153	,	0.44875	,	0.43639	,	0.42443	,	0.41288	,	0.4017	,	0.39089	,	0.38043	,	0.37031,
    0.36052	,	0.35105	,	0.34189	,	0.33303	,	0.32445	,	0.31616	,	0.30813	,	0.30035	,	0.29284	,	0.28556,
    0.27852	,	0.2717	,	0.26511	,	0.25873	,	0.25255	,	0.24658	,
};

/**
 * @brief 二分法查找算法
 * 
 * @param elem 待查找数据
 * @param table 数据表
 * @param table_size 数据表大小
 * @return int -1:参数错误 >=0:待查找元素所在表中位置
 */
int search_dichotomy(float elem, const float *table, int table_size)
{
    int mid = 0, left = 0, right = 0;

    if (table == NULL || table_size == 0)
        return -1;

    left = 0;
    right = table_size - 1;
    mid = (left + right) / 2;

    if (elem > table[0])
    {
        return 0;
    }
    else if (elem < table[table_size - 1])
    {
        return right;
    }

    while (left < right)
    {
        if (elem > table[mid])
        {
            right = mid;
            mid = (left + right) / 2;
        }
        else if (elem < table[mid])
        {
            left = mid;
            mid = (left + right) / 2;
        }
        else if (elem == table[mid])
        {
            break;
        }

        if (left == mid)
        {
            break;
        }
        else if (right == mid)
        {
            mid += 1;
            break;
        }
    }

    printf("mid:%d left:%d right:%d\n", mid, left, right);

    return mid;
}

int main(int argc, char **argv)
{
    printf("*************************\n");
    printf("二分法排序\n");
    printf("*************************\n");

    int temp = 0;
    temp = search_dichotomy(17.5f, temperature, sizeof(temperature));
    temp -= 40;
    printf("temperature is:%d\n", temp);

    return 0;
}

2. 插值查找(比例查找)

2.1 算法叙述

插值查找是在二分查找上的一种改进,但是这种改进需要使用在合适的场景,否则改进之后查找速度反而会有所下降

从来没有最好的算法,只有最合适的算法!

插值查找,又称之为比例查找,在二分查找的基础上进行修改,二分查找中 mid 的值永远是 left~right 的一半,而插值查找也就是根据需要查找的数据在 left ~ right 中间的比例去设置 mid 的值

插值查找适用于线性度比较高的目标数据中查找,线性度越高,效果越好;线性度越低,效果越差

2.2 实例说明

我们在 二分查 找的基础上进行修改,代码如下:

#include <stdio.h>

/* ntc -40 ~ 125℃ 阻值 */
const float temperature[] = {
    /* 249.76	,	233.36	,	218.16	,	204.07	,	190.98	,	178.8	,	167.47	,	156.9	,	147.06	,	137.88,
    129.31	,	121.31	,	113.85	,	106.88	,	100.38	,	94.3	,	88.626	,	83.325	,	78.372	,	73.743,
    69.417	,	65.372	,	61.589	,	58.05	,	54.738	,	51.638	,	48.735	,	46.015	,	43.466	,	41.075,
    38.833	,	36.729	,	34.753	,	32.897	,	31.153	,	29.513	,	27.97	,	26.518	,	25.151	,	23.863,
    22.649	,	21.505	,	20.426	,	19.407	,	18.445	,	17.537	,	16.679	,	15.868	,	15.101	,	14.375,
    13.688	,	13.039	,	12.423	,	11.84	,	11.288	,	10.765	,	10.269	,	9.7981	,	9.3517	,	8.9281,
    8.5261	,	8.1445	,	7.7821	,	7.4379	,	7.1108	,	6.8	    ,	6.5046	,	6.2237	,	5.9565	,	5.7024,
    5.4606	,	5.2305	,	5.0115	,	4.8029	,	4.6043	,	4.4151	,	4.2348	,	4.063	,	3.8992	,	3.743,
    3.594	,	3.4519	,	3.3163	,	3.1869	,	3.0633	,	2.9453	,	2.8326	,	2.7249	,	2.6219	,	2.5235,
    2.4294	,	2.3394	,	2.2533	,	2.1709	,	2.0921	,	2.0166	,	1.9442	,	1.8749	,	1.8086	,	1.7449,
    1.6839	,	1.6254	,	1.5693	,	1.5154	,	1.4637	,	1.4141	,	1.3664	,	1.3207	,	1.2767	,	1.2344,
    1.1938	,	1.1548	,	1.1172	,	1.0811	,	1.0463	,	1.0128	,	0.98063	,	0.94961	,	0.91974	,	0.89097,
    0.86325	,	0.83654	,	0.81079	,	0.78596	,	0.76202	,	0.73894	,	0.71667	,	0.69518	,	0.67444	,	0.65443,
    0.63512	,	0.61647	,	0.59846	,	0.58107	,	0.56427	,	0.54804	,	0.53237	,	0.51722	,	0.50258	,	0.48843,
    0.47475	,	0.46153	,	0.44875	,	0.43639	,	0.42443	,	0.41288	,	0.4017	,	0.39089	,	0.38043	,	0.37031,
    0.36052	,	0.35105	,	0.34189	,	0.33303	,	0.32445	,	0.31616	,	0.30813	,	0.30035	,	0.29284	,	0.28556,
    0.27852	,	0.2717	,	0.26511	,	0.25873	,	0.25255	,	0.24658	,*/
    20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,
};

/**
 * @brief 二分法查找算法
 * 
 * @param elem 待查找数据
 * @param table 数据表
 * @param table_size 数据表大小
 * @return int -1:参数错误 >=0:待查找元素所在表中位置
 */
int search_dichotomy(float elem, const float *table, int table_size)
{
    int mid = 0, left = 0, right = 0;
    int count = 0;

    if (table == NULL || table_size == 0)
        return -1;

    left = 0;
    right = table_size - 1;
    // mid = (left + right) / 2;    //二分法查找
    mid = left + ((elem - table[left]) / (table[right] - table[left])) * (right - left);    //插值查找

    if (elem > table[0]) {
        return 0;
    } else if (elem < table[table_size - 1]) {
        return right;
    }

    while (left < right) {
        if (elem > table[mid]) {
            right = mid;
        } else if (elem < table[mid]) {
            left = mid;
        } else if (elem == table[mid]) {
            break;
        }

        mid = left + ((elem - table[left]) / (table[right] - table[left])) * (right - left);//插值查找
        // mid = (left + right) / 2;    //二分法查找

        if (left == mid) {
            break;
        } else if (right == mid) {
            mid += 1;
            break;
        }

        count ++;
    }

    printf("mid:%d left:%d right:%d\n", mid, left, right);
    printf("count = %d\n", count);

    return mid;
}

int main(int argc, char **argv)
{
    printf("=========================\n");
    printf("          插值查找        \n");
    printf("*************************\n");

    int temp = 0;
    temp = search_dichotomy(17.5f, temperature, sizeof(temperature)/sizeof(temperature[0]));
    temp -= 40;
    printf("temperature is:%d\n", temp);

    return 0;
}

上述为插值插值的实现,和二分法查找相比,唯一修改了mid的计算过程

此外我们对针对插值查找仅适用于线性度比较高的目标数据,这一结论进行验证:

  • 当目标数据线性度非常好,斜率k为1的集合

    • 采用插值查找,查找次数:0次
    • 在这里插入图片描述
    • 采用二分查找,查找次数:3次
    • 在这里插入图片描述
  • 当目标数据线性度较差,我们还用第一节中的NTC电阻值的数据,其数据线性度如下:在这里插入图片描述

    • 采用插值查找,查找次数:24次!
    • 在这里插入图片描述
    • 采用二分查找,查找次数:7次
    • 在这里插入图片描述

由此可见,根据目标数据/群体合理的使用算法尤为重要

3. 斐波那契查找(黄金分割法查找)

3.1 算法叙述

黄金分割法查找,和二分查找思想相差不多,也是通过不断的缩小数据所在的范围来查找所需要的数据。

黄金分割法,由于其mid每次的取值均在黄金分割点上,因此称之为黄金分割法查找。

而由于斐波拉契数列,连续的三个数十分接近黄金分割点,如 斐波拉契数列:

1 1 2 3 5 8 13 21 34 55 89 144 233

任选连续的三组数,如 13 21 34(34 - 21)/ (34 - 13) = 0.619 此数值十分接近黄金分割点,因此斐波拉契分割法也称之为黄金分割法(pass: 当然前面那几组1 1 2误差会大点)

那么如何实现斐波拉契查找呢?

  1. 第一步:首先定义斐波拉契数组 F[],并定义变量k

  2. 第二步:找到 F[k] 刚好大于待查找数组table的长度

  3. 之后按照下图所示进行循环比较
    在这里插入图片描述

  4. 第四步:直至 k < 0left < righttable[left] = elemtable[right] = elem 任意一条件满足,结束循环

3.2 实例说明

我们依旧以第1节中的NTC电阻为例,采用斐波拉契查找对NTC电阻阻值对应的温度进行查找,代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* ntc -40 ~ 125℃ 阻值 */
const float temperature[] = {
    249.76	,	233.36	,	218.16	,	204.07	,	190.98	,	178.8	,	167.47	,	156.9	,	147.06	,	137.88,
    129.31	,	121.31	,	113.85	,	106.88	,	100.38	,	94.3	,	88.626	,	83.325	,	78.372	,	73.743,
    69.417	,	65.372	,	61.589	,	58.05	,	54.738	,	51.638	,	48.735	,	46.015	,	43.466	,	41.075,
    38.833	,	36.729	,	34.753	,	32.897	,	31.153	,	29.513	,	27.97	,	26.518	,	25.151	,	23.863,
    22.649	,	21.505	,	20.426	,	19.407	,	18.445	,	17.537	,	16.679	,	15.868	,	15.101	,	14.375,
    13.688	,	13.039	,	12.423	,	11.84	,	11.288	,	10.765	,	10.269	,	9.7981	,	9.3517	,	8.9281,
    8.5261	,	8.1445	,	7.7821	,	7.4379	,	7.1108	,	6.8	    ,	6.5046	,	6.2237	,	5.9565	,	5.7024,
    5.4606	,	5.2305	,	5.0115	,	4.8029	,	4.6043	,	4.4151	,	4.2348	,	4.063	,	3.8992	,	3.743,
    3.594	,	3.4519	,	3.3163	,	3.1869	,	3.0633	,	2.9453	,	2.8326	,	2.7249	,	2.6219	,	2.5235,
    2.4294	,	2.3394	,	2.2533	,	2.1709	,	2.0921	,	2.0166	,	1.9442	,	1.8749	,	1.8086	,	1.7449,
    1.6839	,	1.6254	,	1.5693	,	1.5154	,	1.4637	,	1.4141	,	1.3664	,	1.3207	,	1.2767	,	1.2344,
    1.1938	,	1.1548	,	1.1172	,	1.0811	,	1.0463	,	1.0128	,	0.98063	,	0.94961	,	0.91974	,	0.89097,
    0.86325	,	0.83654	,	0.81079	,	0.78596	,	0.76202	,	0.73894	,	0.71667	,	0.69518	,	0.67444	,	0.65443,
    0.63512	,	0.61647	,	0.59846	,	0.58107	,	0.56427	,	0.54804	,	0.53237	,	0.51722	,	0.50258	,	0.48843,
    0.47475	,	0.46153	,	0.44875	,	0.43639	,	0.42443	,	0.41288	,	0.4017	,	0.39089	,	0.38043	,	0.37031,
    0.36052	,	0.35105	,	0.34189	,	0.33303	,	0.32445	,	0.31616	,	0.30813	,	0.30035	,	0.29284	,	0.28556,
    0.27852	,	0.2717	,	0.26511	,	0.25873	,	0.25255	,	0.24658	,
};

int create_fibonacci(unsigned int **fib_table, int buf_size)
{
    int tmp[3] = {1, 1, 2};
    int count = 3;
    unsigned int *table  = NULL;

    if (buf_size < 2) {
        printf("buf too short!\n");
        return -1;
    }

    while (tmp[2] < buf_size) {
        tmp[0] = tmp[1];
        tmp[1] = tmp[2];
        tmp[2] = tmp[0] + tmp[1];
        count ++;
    }

    table = (unsigned int *)malloc(count * sizeof(unsigned int));
    memset(table, 0, count * sizeof(unsigned int));

    table[0] = 1;
    table[1] = 1;
    for (int i = 2; i < count; i++) {
        table[i] = table[i - 1] + table[i -2];
    }
    *fib_table = table;
    return count;
}

int search_fibonacci_with_fib(const int *fib_table, float elem,const float *table, int table_size)
{
    int k = 0;
    int left = 0, right = 0, mid = 0;
    int count = 0;

    while (fib_table[k] < table_size) {
        k ++;
    }

    right = table_size - 1;
    left = 0;

    //可增加针对elem在right右侧情况单独处理

    while (left < right) {
        mid = left + fib_table[k - 1];
        
        if (table[mid] > elem) {        /* 目标元素在黄金分割线右侧 注意:此处由于table是降序 所以是'>' !!! */
            left = mid;
            k = k - 1;
        } else if (table[mid] < elem) { /* 目标元素在黄金分割线左侧 注意:此处由于table是降序 所以是'<' !!! */
            right = mid;
            k = k - 2;
        } else {
            break;
        }

        if (k < 0) {
            mid = left; /* 温度取低温测值 */
            break;
        }
        count ++;
    }
    printf("count: %d\n", count);
    return mid;
}

int main(int argc, char **argv)
{
    printf("\nfibonacci search:\n");
    unsigned int *fib_table = NULL;
    int count = 0;
    int temp = 0;

    /* 根据所需数据动态创建斐波拉契数列 */
    count = create_fibonacci(&fib_table, sizeof(temperature)/sizeof(temperature[0]));
    for (int i = 0; i < count; i ++)
        printf("%d ", fib_table[i]);
    printf("\n");

    /* 斐波拉契查找 */
    temp = search_fibonacci_with_fib(fib_table, 17.5f, temperature, sizeof(temperature)/sizeof(temperature[0]));
    temp -= 40;
    printf("temperature is:%d\n", temp);
    return 0;
}

运行结果如下:

fibonacci search:
1 1 2 3 5 8 13 21 34 55 89 144 233 
count: 8
temperature is:5

在上述代码中,采用create_fibonacci 根据待查找数组大小动态创建斐波拉契数组,需要注意的是,我们这采用的是NTC电阻,由于NTC电阻的特性,待查找数组是降序,因此斐波拉契查找运算中的大小比较符号需要特别注意!!

采用第一节中的实例,同样的目标数组,这是我们进行对比实验之后的运行结果:

dichotomy search:
mid:45 left:45 right:46
count = 24
temperature is:5

fibonacci search:
1 1 2 3 5 8 13 21 34 55 89 144 233
count: 8
temperature is:5

通过此结果中两者循环查找的次数进行比较我们可以发现:采用斐波拉契查找只循环了8次便找到了目标数据,而二分查找需要循环24次,斐波拉契查找远胜于二分查找!!!

同样,如果待查找数据数据更大,斐波拉契的效率会要更高!

4. 线性索引查找

4.1 算法叙述

//TODO

4.2 实例说明

//TODO


由于精力有限,因此本博文将分多次更新,感谢您的关注!

创作不易,转载请注明出处!

关注、点赞+收藏,可快速查看后续分享哦!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱出名的狗腿子

你的鼓励就是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值