• 【经典算法学习-排序篇】顺序查找


    文章目录 请放心食用

    一、什么是算法

    1.算法的定义

    2.一些补充的概念

    二、顺序查找

    1.元素怎么查

    2.顺序查找

    3.时间复杂度分析

    4.代码优化

     三、总结

    四、课后小练习

     五、下期预告


    一、什么是算法

    算法,是一种高深的学问。或许有人说,算法,就是几个字母,algorithm。不得不说这也对

    不过,这样的理解不免得有些浅显。我们不妨换一种说法:

    1.算法的定义

    在一篇经典的教材《Introduction.to.Algorithms》的开篇,这样写道:

    Informally, an algorithm is any well-defined computational procedure that takes some value, or set of values, as input and produces some value, or set of values, as output. An algorithm is thus a sequence of computational steps that transform the input into the output.

    (非正式地说,算法是任何定义明确的计算过程,它将某些值或一组值作为输入,并产生某些值或值组作为输出。因此,算法是将输入转换为输出的一系列计算步骤。)

     在这段文字里,我们看到的最多的两个词语就是:输入输出。这时候又会有人说:

    《啊对对对》

    A:啊哈,算法就是这种东西?输入,不就是什么cinscanf这些吗?输出,不就是coutprintf这类的吗?顶多,再用个read()这类的快读不是吗?算法可真简单!

    B:啊对对对,啊对对对,阿对对对!(我可不会说我是在水字数)

     看的简单一点,这段话就是这个意思。但是,我们如果看的深远一点,那么就是:算法是一种解决问题的工具,我们只需要把输入数据和输出数据描述清楚了,我们的算法就能够实现。这也是说我们的算法要明确我们有什么要什么

    2.一些补充的概念

    • 数据结构

    在实现一个算法的时候,我们大部分时候都会用到一种东西:数据结构。在实现同一种算法的时候,我们如果使用不同的数据结构,那么我们得到的效率(例如时间复杂度、空间复杂度等)也会截然不同。

    我们不妨举个例子:以我们最熟悉的排序为例。我们可以使用数组,实现冒泡排序、桶排序、插入排序;我们也可以使用来实现树型排序;我们还可以使用一堆变量来实现啥也不是排序······

    数据结构不同,效果截然不同。我们在日常中常见的数据结构包括:数组链表队列等等。

    • 算法效率

    在我们实现一个算法的时候,我们需要考虑到这个算法的效率问题,简称算法效率

    我们如何判断一个算法的好坏?我们就是通过算法效率来判断的。一个好的算法,可以大幅减少程序的使用时间资源消耗

    那么,我们如何判断(计算)算法的效率呢?我们不需要进行精确的计算(虽然也可以进行精确计算,只不过没有必要),只需要引入时间复杂度空间复杂度两个概念就可以解决。综合评估两个复杂度以达到最优,就能实现估算算法效率的好坏了。

    • 时间复杂度

    我们将算法中基本操作重复执行的频率称为时间复杂度(基本操作一般指程序中最深层循环中的语句,一般包括一些赋值语句、运算语句等)。

    一般的,时间复杂度和程序中的循环息息相关。循环的层数越多,那么程序的时间复杂度就越高;循环的层数越少,那么程序的时间复杂度就越低。这也是我们为什么说暴力枚举的时间复杂度贼高。

    怎么计算时间复杂度?对于每一个程序,我们等可以将其转化为常数或与n相关的函数表达式,记作f(n)。我们如果把每一段程序所花费的时间相加,就会得到一个非常复杂的表达式,在合并的时候保留量级最大的部分就可以确定时间复杂度了,记作O(f(n))。这里的O就是代表数量级

    常见的时间复杂度有:O(1),O(log_{2}n),O(n), O(n_{log_{2}}n),O(n^{2}),O(n^3),O(2^n),O(n!)等。

    • 空间复杂度

    空间复杂度,顾名思义,就是程序从开始执行到结束所用的内存容量,即整个的过程需要用到多少内存容量 。为了更好的评估算法的本身,我们在计算的时候将输入的数据排除在外。在计算时,我们更考虑算法在运行的时候需要额外定义多少的变量(存储结构)。

    例如,我们如果要实现一个两数相加的程序,那么所用的空间复杂度为O(1)。

    二、顺序查找

    1.元素怎么查

    查找元素,又称为检索,如果找到满足条件的数,那么就是查找成功,反之则是查找失败。

    怎么查找?查找的方式多种多样,我们以一道例题为例,来给大家详细讲解。

    【深基4.例2】找最小值 - 洛谷

    •  顺序查找

    顺序查找,也称为线性查找。我们从数组的一边开始,逐个查找。如果和目标元素相同,则查找成功。反之如果没有找到目标元素,那么就查找失败。

    1. //样例代码-顺序查找
    2. #include
    3. using namespace std;
    4. int a[1001], n;
    5. int _max;
    6. int main()
    7. {
    8. cin >> n;
    9. for(int i=0;i
    10. {
    11. cin >> a[i];
    12. }
    13. for(int i=0;i
    14. {
    15. _max = max(_max, a[i]);
    16. }
    17. cout << _max <
    18. return 0;
    19. }
    • 折半查找

    折半查找,即二分查找,也就是我们常说的“二分法”。这是一种效率较高的代码。算法的核心是每一次搜尽可能的缩小搜索空间。我们会在后面的文章中提到的。

    • 索引查找

    索引查找,主要分为基本索引查找分块查找。对于无序的一组数,建立索引表,使索引表或分块有序,结合顺序查找,已完成搜索。这个我们后文提到。

    • 随机查找

    啊这,这还是算了吧。懂得都懂啊,这不是个算法~

    2.顺序查找

    例题1:查找3

    给定一组长度为n的数字序列,寻找这组序列是否有“3”这个数字。如果有,则输出数字3的下标;若没有,则输出-1。若一组序列中出现多个相同的数字,则优先输出靠前的下标。

    【输入】

    第一行一个数字n,代表序列长度。

    第二行,输入该序列。

    【输出】

    输出数字3所在的下标,若未查找到则输出-1。

    【输入样例#1】

    5

    5 4 3 2 1

    【输出样例#1】

    2

    【输入样例#2】

    5

    7 5 4 6 9

    【输出样例#2】

    -1

    【数据规模与提示】

    1\leq n\leq 100

    第一步:输入数字

    这一步没啥可说的,就按照题意来写就行了呗。

    1. #include
    2. using namespace std;
    3. int main()
    4. {
    5. int n;
    6. int a[101]; // 由于n的规模最大到100,所以申请101长度,不过分吧?
    7. cin >> n;
    8. for (int i = 0; i < n; i++)
    9. {
    10. cin >> a[i];
    11. }
    12. return 0;
    13. }

    第二步:顺序查找

    这里就是关键:怎么查?

    前文说过,顺序查找就是线性查找,从数组的一边开始逐个查找。那么,我们就来试试吧。

    首先,我们需要考虑到,我们需要一个循环。

    for (int i = 0; i < n; i++)

    循环体会是什么?判断是否为3。如果不是3,就continue,继续查找。如果是的话,就输出这个下标,return出去。我们不妨写出来这段代码:

    1. for (int i = 0; i < n; i++)
    2. {
    3. if (a[i] != 3)
    4. {
    5. continue;
    6. }
    7. else
    8. {
    9. cout << i << endl;
    10. return 0;
    11. }
    12. }

    第三步:拼接

    我们将主要的代码写完之后,就需要完成代码拼接工作了。拼接完成后,代码如下:

    1. #include
    2. using namespace std;
    3. int main()
    4. {
    5. int n;
    6. int a[101];
    7. cin >> n;
    8. for (int i = 0; i < n; i++)
    9. {
    10. cin >> a[i];
    11. }
    12. for (int i = 0; i < n; i++)
    13. {
    14. if (a[i] != 3)
    15. {
    16. continue;
    17. }
    18. else
    19. {
    20. cout << i << endl;
    21. return 0;
    22. }
    23. }
    24. cout << "-1" << endl;
    25. return 0;
    26. }

    3.时间复杂度分析

    • 最优情况

    最优的情况,也就是说所有能不被执行的代码都不会被执行以达到最优。什么时候会产生最优?当序列中的第一个数字就是3时,此时的情况便是最优的。所以,最优情况为O(1)

    像这段代码的最优情况这样的是很少见的。大多数的代码的最优情况也仅仅是O(n)而已。

    • 最差情况

    这是我们最不愿意看到的情况了。但是,我们还是需要考虑。当循环被完全拉满,却一个3都没有找到的时候,此时便是最差的情况了。

    在这种情况下,循环被执行满了。所以,此时的时间复杂度也就顺理成章的成为O(n)了。

    • 平均情况 

    综合最优情况和最差情况,时间复杂度为:\frac{(O(1)+O(n))}{2} = \frac{(O(1 + n))}{2} = O(n)

     这样的时间复杂度不算特别高,所以这段代码不用做什么优化了。

    4.代码优化

    那么,如果我们真的要优化的话,如何优化?

    我们不妨回顾我们的代码。我们的for循环每一次都要对数组是否越界而判断。这样的判断是否可以省去?如何省去?

    我们可以使用一个while循环来解决。我们用脑洞来想到了这段代码:

    (仔细想想,还真的是妙呀!)

    1. int i = n;
    2. while (a[i] != 3)
    3. {
    4. i--;
    5. }
    6. cout << i << endl;

    这段代码优化后,本质上时间复杂度没有改变。所以,此处优化的作用不是很大,但是优化后可能会产生一些微小的效果。

    附上优化后的全部代码:

    1. #include
    2. using namespace std;
    3. int main()
    4. {
    5. int n;
    6. int a[101];
    7. cin >> n;
    8. int i = n;
    9. while (a[i] != 3)
    10. {
    11. i--;
    12. }
    13. cout << i << endl;
    14. return 0;
    15. }

     三、总结

    代码是否优化,我们使用的方法却是一致的——顺序查找,又称为线性查找

    线性查找的中心思想其实就是逐个寻找,直到找到目标。这就有些类似于暴力枚举的思想。

    线性查找作为我们代码中最常用的查找方式,如今也是被广泛使用。学好线性查找,你就已经能够解决所有关于查找类的问题了。

    四、课后小练习

    光听不练,怎行?

    奉上今日的课后题单,各位大佬们一定要接受这份提题单!

    求知若渴,虚心若愚。

    有一门课不及格的学生 - 洛谷

    不定方程求解 - 洛谷

    [NOIP2002 提高组] 字串变换 - 洛谷(较难)

     五、下期预告

    顺序查找,太慢!这是什么东西,时间复杂度这么高!

    我比你少一半的时间!大哥!

    我把你砍了一半!

    (猜猜下期讲什么)

  • 相关阅读:
    C++仿函数
    MMDetection 使用示例:从入门到出门
    设计模式之备忘录模式
    【PyTorch】深度学习实践之反向传播 Back Propagation
    【C语言】2.数组与指针
    桥接模式(Bridge Pattern)
    将同级数据处理成树形数据
    LeetCode每日一题——Maximum Sum With Exactly K Elements
    Linux日志管理logrotate日志轮转
    python实现ftp服务端和客户端
  • 原文地址:https://blog.csdn.net/Wanghs0716/article/details/126150359