❤️ 五彩斑斓的「 算法 」世界需要多「 思考 」 ❤️(建议收藏)

Posted 英雄哪里出来

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了❤️ 五彩斑斓的「 算法 」世界需要多「 思考 」 ❤️(建议收藏)相关的知识,希望对你有一定的参考价值。

零、📃前言

  养成 「经常思考」 的习惯,在面临难题的时候,才能临危不乱,泰山崩于前而面不改色。
  这篇文章中,我会着重讲解一些常见的 「算法和数据结构」 的设计思想,并且配上动图。希望读者能够带上自己的 「思考」,主要针对面试中常见的问题和新手朋友们比较难理解的点进行解析。当然,这个系列的文章,后面也会给出面向算法竞赛的提纲,如果有兴趣深入学习的欢迎在「评论区留言,一起成长交流」

  • 零基础学算法的最好方法,莫过于刷题了。当然,刷题是不够的,刷的过程中也要多多总结,多多思考,养成 「经常思考」 的习惯,这就是所谓的 「 流水不腐,户枢不蠹 」,任何事情都是需要坚持的,刷题也一样,没有刷够足够的题,就很难做出系统性的总结。所以上大学的时候,我花了三年的时间来刷题, 工作以后还是会抽点时间出来刷题。

千万不要用工作忙来找借口,时间挤一挤总是有的

  • 当然,每天不需要花太多时间在这个上面,把这个事情做成一个规划,按照长期去推进。反正也没有 KPI 压力,就当成是工作之余的一种消遣,还能够提升思维能力。

所以,无论你是 小学生中学生高中OIer大学ACMer职场人士,只要想开始,一切都不会太晚!

  「 插入排序 」 是比较好理解且编码相对简单的排序算法,虽然效率不是很高,但一般也出现在各种 「数据结构」 的教科书上。于是,我来了。我会尽量做到「深入浅出」,让 90%「零基础小白 」 也都能理解,真正做到 「让天下没有难学的算法」 。我知道这很难,但是我愿意尝试!我会尽量把文章写得有趣,希望读者朋友给个面子,帮我完成阅读,不能完读,我可就完犊子了 🤣,当然,如果你觉得还可以,给我个「 赞 」「 收藏 」,这个对我很重要,谢谢了!

🙉饭不食,水不饮,题必须刷🙉

C语言免费动漫教程,和我一起打卡!
🌞《光天化日学C语言》🌞

LeetCode 太难?先看简单题!
🧡《C语言入门100例》🧡

数据结构难?不存在的!
🌳《数据结构入门》🌳

LeetCode 太简单?算法学起来!
🌌《夜深人静写算法》🌌

那么,我的教程和别人的教程有什么不同的地方呢?
  「第一步」简单释义: 我会简单解释一下这个算法的目的、思想、以及为什么叫这个名字以帮助记忆。
  「第二步」核心思想: 我会大致介绍一下这个算法的核心思想。
  「第三步」动图演示: 我会引入一个动图,并且用一个切实的例子展示一下算法执行的全过程。
  「第四步」算法前置: 在学习这个算法之前,我们需要学习的前置内容有哪些?这些内容是需要事先去攻克的。
  「第五步」算法描述: 细致的讲解整个算法的执行流程。
  「第六步」算法分析: 对算法的时间复杂度和空间复杂度进行一个详细的分析。
  「第七步」优化方案: 介绍一些可以优化的点。
  「第八步」代码实践: 用 C/C++ 来实现上述算法。
  「第九步」代码验证: 最后,我会推荐一些比较好用的在线评测系统来验证我们实现的算法的正确性。

一、🎯简单释义

1、算法目的

  将原本乱序的数组变成有序,可以是 「升序」 或者 「降序」 (为了描述统一,本文一律只讨论 「 升序」 的情况)。

2、算法思想

  通过不断将当前元素 「插入」 「升序」 序列中,直到所有元素都执行过 「插入」 操作,则算法结束。

3、命名由来

  每次都是将元素 「插入」 到 有序 序列中,故此命名 「 插入排序 」

二、🧡核心思想

  • 「迭代」:类似的事情,不停地做。
  • 「比较」:关系运算符 小于等于( ≤ \\le ) 的运用。
  • 「移动」:原地后移元素。

三、🔆动图演示

1、样例

856437102
  • 初始情况下的数据如 图二-1-1 所示,基本属于乱序,纯随机出来的数据。

图二-1-1

2、算法演示

  • 接下来,我们来看下排序过程的动画演示。如 图二-2-1 所示:

图二-2-1

3、样例说明

图示含义
■ 的柱形代表尚未排好序的数
■ 的柱形代表正在执行 比较 和 移动 的数
■ 的柱形代表已经排好序的数
■ 的柱形代表待执行插入的数

  我们看到,首先需要将 「第二个元素」「第一个元素」 进行 「比较」,如果 前者 小于等于 后者,则将 后者 进行向后 「移动」前者 则执行插入;
  然后,进行第二轮「比较」,即 「第三个元素」「第二个元素」、「第一个元素」 进行 「比较」, 直到 「前三个元素」 保持有序 。
  最后,经过一定轮次的「比较」「移动」之后,一定可以保证所有元素都是 「升序」 排列的。

四、🌳算法前置

1、循环的实现

  • 这个算法本身需要做一些「 循环 」进行迭代计算,所以你至少需要知道「 循环 」 的含义,这里以 「 c++ 」 为例,来看下一个简单的「 循环 」是怎么写的。代码如下:
int n = 520;
for(int i = 0; i < n; ++i) {
    // TODO : 。。。
}
  • 这个语句就是一个最简单的循环语句,它会将循环体内的语句执行 n n n 次,而这里的 n n n 等于 1314 1314 1314,也就是会执行 1314 1314 1314 次。

2、比较的实现

  • 「比较」两个元素的大小,可以采用关系运算符,本文我们需要排序的数组是按照 「升序」 排列的,所以用到的关系运算符是 「小于等于运算符(即 <=)」
  • 我们可以将两个数的「比较」写成一个函数smallerEqualThan,以 「 c++ 」 为例,实现如下:
#define Type int
bool smallerEqualThan(Type a, Type b) {
    return a <= b;
}
  • 其中Type代表数组元素的类型,可以是整数,也可以是浮点数,也可以是一个类的实例,这里我们统一用int来讲解,即 32位有符号整型。

3、移动的实现

  • 所谓「移动」,其实是将某个元素执行后移,实现如下:
a[j + 1] = a[j];

五、🥦算法描述

1、问题描述

  给定一个 n n n 个元素的数组,数组下标从 0 0 0 开始,采用「 插入排序 」将数组按照 「升序」排列。

2、算法过程

整个算法的执行过程分以下几步:
  1) 循环迭代变量 i = 1 → n − 1 i = 1 \\to n-1 i=1n1
  2) 每次迭代,令 x = a [ i ] x = a[i] x=a[i] j = i − 1 j = i-1 j=i1,循环执行比较 x x x a [ j ] a[j] a[j],如果产生 x ≤ a [ j ] x \\le a[j] xa[j] 则执行 a [ j + 1 ] = a [ j ] a[j+1] = a[j] a[j+1]=a[j]。然后执行 j = j + 1 j = j + 1 j=j+1,继续执行 2);否则,跳出循环,回到 1)

六、🧶算法分析

1、时间复杂度

  • 我们假设 「比较」「移动」 的时间复杂度为 O ( 1 ) O(1) O(1)
  • 「 插入排序 」 中有两个嵌套循环。

外循环正好运行 n − 1 n-1 n1 次迭代。 但内部循环运行变得越来越短:
  当 i = 1 i = 1 i=1,内层循环 1 1 1「比较」操作。
  当 i = 2 i = 2 i=2,内层循环 2 2 2「比较」操作。
  当 i = 3 i = 3 i=3,内层循环 3 3 3「比较」操作。
  ……
  当 i = n − 2 i = n-2 i=n2,内层循环 n − 2 n-2 n2「比较」操作。
  当 i = n − 1 i = n-1 i=n1,内层循环 n − 1 n-1 n1「比较」操作。

  • 因此,总「比较」次数如下:
  • 1 + 2 + . . . + ( n − 1 ) = n ( n − 1 ) 2 1 + 2 + ... + (n-1) = \\frac {n(n-1)}{2} 1+2+...+(n1)=2n(n1)
  • 总的时间复杂度为: O ( n 2 ) O(n^2) O(n2)

2、空间复杂度

  • 由于算法在执行过程中,只有「移动」变量时候,需要事先将变量存入临时变量x,而其它没有采用任何的额外空间,所以空间复杂度为 O ( 1 ) O(1) O(1)

七、🧢优化方案

  「 插入排序 」在众多排序算法中效率较低,时间复杂度为 O ( n 2 ) O(n^2) O(n2)
   想象一下,当有 n = 1 0 5 n = 10^5 n=105 个数字。 即使我们的计算机速度超快,并且可以在 1 秒内计算 1 0 8 10^8 108 次操作,但冒泡排序仍需要大约一百秒才能完成。
  考虑,在进行插入操作之前,我们找位置的过程是在有序数组中找的,所以可以利用「二分查找」 来找到对应的位置。然而,执行 「 插入 」 的过程还是 O ( n ) O(n) O(n),所以优化的也只是常数时间,最坏时间复杂度是不变的。

  • 「改进思路」执行插入操作之前利用 「 插入 」 来找到需要插入的位置。

八、💙代码实践

#include <stdio.h>

int a[1010];

void input(int n, int *a) {
    for(int i = 0; i < n; ++i) {
        scanf("%d", &a[i]);
    }
}

void output(int n, int *a) {
    for(int i = 0; i < n; ++i) {
        if(i)
            printf(" ");
        printf("%d", a[i]);
    }
    puts("");
}

void swap(int *a, int *b) {
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

void InsertSort(int n, int *a) {       // (1)
    int i, j; 
    for(i = 1; i < n; ++i) {
        int x = a[i];                  // (2)
        for(j = i-1; j >= 0; --j) {    // (3)
            if(x <= a[j]) {            // (4)
                a[j+1] = a[j];         // (5)
            }else
                break;                 // (6)
        }
        a[j+1] = x;                    // (7)
    }
} 

int main() {
    int n;
    while(scanf("%d", &n) != EOF) {
        input(n, a);
        InsertSort(n, a);
        output(n, a);
    }
    return 0;
} 
  • ( 1 ) (1) (1) void InsertSort(int n, int *a)插入排序 的实现,代表对a[]数组进行升序排序。
  • ( 2 ) (2) (2) 此时a[i]前面的 i-1个数都认为是排好序的,令x = a[i]
  • ( 3 ) (3) (3) 逆序的枚举所有的已经排好序的数;
  • ( 4 ) (4) (4) 如果枚举到的数a[j]比需要插入的数x大,则当前数往后挪一个位置;
  • ( 5 ) (5) (5) 执行挪位置的 O ( 1 ) O(1) O(1) 操作;
  • ( 6 ) (6) (6) 否则,跳出循环;
  • ( 7 ) (7) (7)x插入到合适位置;

九、💗代码验证

  • 比如,你可以在百度上搜索 代码在线提交OnlineJudgeLeetCode洛谷HDOJPOJ 等等的关键词,然后去找对应的题目提交验证你的代码的正确性。

🙉好了,今天的内容就到这里了,你学废了吗?!🙉

🙉饭不食,水不饮,题必须刷🙉

C语言免费动漫教程,和我一起打卡!
🌞《光天化日学C语言》🌞

LeetCode 太难?先看简单题!
🧡《C语言入门100例》🧡

数据结构难?不存在的!
🌳《数据结构入门》🌳

LeetCode 太简单?算法学起来!
🌌《夜深人静写算法》🌌

以上是关于❤️ 五彩斑斓的「 算法 」世界需要多「 思考 」 ❤️(建议收藏)的主要内容,如果未能解决你的问题,请参考以下文章

[bzoj 5143][Ynoi 2018]五彩斑斓的世界

[Ynoi2018]五彩斑斓的世界

题解 P4117 [Ynoi2018]五彩斑斓的世界

我们的大脑,究竟是怎么建立稳定视觉世界的?

[Ynoi2018]五彩斑斓的世界

OpenCV ⚠️高手勿入! 半小时学会基本操作 24⚠️ SIFT 算法