程序员最近都爱上了这个网站  程序员们快来瞅瞅吧!  it98k网:it98k.com

本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长

+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

❤️学姐说她用 8 行代码写了 8 个算法(上)❤️(推荐收藏)

发布于2021-06-07 20:56     阅读(885)     评论(0)     点赞(16)     收藏(2)


一、前言

  本文适合对算法处于朦胧期的初学者,文字浅显易懂,并且配有生动有趣的动图,也是作者呕心沥血之作,希望对刚入大学,或者职场上想要涉足算法的青年同僚有所启示。
  学习算法,任何时候都不嫌晚,大不了就是大器晚成而已,所以无论你是30岁,40岁,50岁,甚至60岁,只要下了决心,就已经成功了一半!本文的故事发生在 ❤️学姐教你 10 道题搞定 c 语言❤️ 的两年后,剧情扑朔迷离,作者至今回忆起来还历历在目。

二、朝思暮想

  • 自从上次一别,不知何时才能相见,不免有些感伤。于是,那天晚上,我,辗转反侧,彻夜难眠,寝不安席,食无甘味。
    在这里插入图片描述
    不太聪明的亚子
  • 从来没有一个女孩子可以让我如此朝思暮想,魂牵梦萦。
  • 可能是因为她还没有把她的毕生算法教会给我,怎么一声不吭就人间蒸发了呢!我不甘心!
  • 就算是天涯海角,我也要找到你!
    在这里插入图片描述
  • 终于,在一个夜黑风高的晚上,让我在睡梦中见到了她,梦里的她比现实中还要逗比。竟然给我写下了八行代码!
    在这里插入图片描述
  • 也就是因为那个晚上,成就了我后来的 ❤️《夜深人静写算法》❤️
  • 她告诉我,一行代码代表一个算法!我觉得她在侮辱我的智商!
  • 那天晚上大致是这样的 … …

三、南柯一梦


在这里插入图片描述

四、梦中的梦中

  • 哎,越想越不对劲!
  • 所以我打算继续睡,看看能不能继续梦到学姐。
  • 果然……功夫不负有心人……
  • 学姐出现了!!!

  • 学姐还是像往常一样,心思缜密,替人着想,越来越崇拜她了。

在这里插入图片描述

五、梦中人的梦中

算法一

在这里插入图片描述

【例题1】给定 n ( n ≤ 65535 ) n(n \le 65535) n(n65535),求 ∑ i = 1 n i = 1 + 2 + . . . + n \sum_{i=1}^n i = 1 + 2 + ... + n i=1ni=1+2+...+n

  • 这是一个等差数列!
  • 我直接用等差数列的求和公式就行了。
int sum(int n) {
    return n * (n + 1) / 2;
}

在这里插入图片描述

  • 然后我调试了一下,发现:
    在这里插入图片描述
  • n = 65535 n=65535 n=65535 时,输出的竟然是负数!

原因是因为 n ∗ ( n + 1 ) = 65535 ∗ 65536 = ( 2 16 − 1 ) 2 16 = 2 32 − 2 16 n * (n + 1) = 65535 * 65536 = (2^{16}-1)2^{16} = 2^{32} -2^{16} n(n+1)=6553565536=(2161)216=232216,而 i n t int int 能够表示的最大值为 2 31 − 1 2^{31}-1 2311,所以产生了溢出。就变成了负数。至于为什么溢出会变成负数,可以了解补码相关的知识:c++ 补码详解

  • 这里只需要对 n n n 进行奇偶性判定,将除法放在乘法之前,就可以防止溢出了。即:
  • s u m ( n ) = { ( n + 1 ) / 2 × n n 为 奇 数 n / 2 × ( n + 1 ) n 为 偶 数 sum(n) =
    {(n+1)/2×nnn/2×(n+1)n
    sum(n)={(n+1)/2×nn/2×(n+1)nn
  • c++ 实现如下:
int sum(int n) {
    if(n % 2 == 1) {
        return (n + 1) / 2 * n;
    }else {
        return n / 2 * (n + 1);
    }
}
  • 然后我们再通过三目运算符写成一行代码,如下:
int sum(int n) {
    return (n%2) ? (n+1)/2*n : n/2*(n+1);
}

这里的 condition ? a : b是 c/c++ 中的三目运算符,含义是根据表达式condition的值的真或假,选择返回 a还是 b。由于对于一个数 x x x x x x 非0就是真,为0就是假,所以可以直接省略 x == 0的判断。

在这里插入图片描述

  • 通过这段代码,我了解了 32位整数的溢出、补码表示、三目运算符、表达式真值的省略写法。

算法二

在这里插入图片描述

【例题2】给定 n ( n < 16 ) n(n \lt 16) n(n<16),求 ∏ i = 1 n i = 1 × 2 × . . . × n \prod_{i=1}^n i = 1 \times 2 \times ... \times n i=1ni=1×2×...×n

  • 由于 n n n 比较小,所以我打算直接暴力枚举,大概可以写成这样:
int sum(int n) {
    int s = 1;
    for(int i = 1; i <= n; ++i) {
        s *= i;
    }
    return s;
}
  • 但是学姐说的一行代码好像比较难办到,我继续压缩,把 s这个变量放到循环体内和i一起初始化,并且把乘法和循环放到同一行,变成了下面这副样子。
int sum(int n) {
    for(int s = 1, i = 1; i <= n; ++i) s *= i;
    return s;
}
  • 这时候发现编译不过!!!
    在这里插入图片描述
  • 原因是:s的作用域在循环体内,所以无法在循环体外部进行使用,但是我们这个函数有需要有一个返回值,总不能把函数体给返回吧?这可如何是好!

在这里插入图片描述

  • 由于那时候,我对递归还没有什么概念,所以一脸懵逼。

在这里插入图片描述

  • 我还是听不懂……

  • 这下我就懂了!
  • 我们可以定义这么一个函数 f ( x ) = 1 × 2 × 3 × . . . × x f(x) = 1 \times 2 \times 3 \times ... \times x f(x)=1×2×3×...×x,其中 x ≥ 0 x \ge 0 x0
  • x > 0 x > 0 x>0 时,代入 ( x − 1 ) (x-1) (x1),显然有 f ( x − 1 ) = 1 × 2 × 3 × . . . × ( x − 1 ) f(x-1) = 1 \times 2 \times 3 \times ... \times (x-1) f(x1)=1×2×3×...×(x1)
  • 于是,可以得到:
  • f ( x ) f ( x − 1 ) = x \frac {f(x)} {f(x-1)} = x f(x1)f(x)=x
  • 由于 f ( x − 1 ) > 0 f(x-1) > 0 f(x1)>0, 等式两边可以同时乘上 f ( x − 1 ) f(x-1) f(x1),很容易得出递推公式如下:
  • f ( x ) = { 1 ( x = 0 ) f ( x − 1 ) × x ( x > 0 ) f(x) =
    {1(x=0)f(x1)×x(x>0)
    f(x)={1f(x1)×x(x=0)(x>0)
  • 所以,翻译成 c/c++ 的语言,就可以写成这样:
int f(int x) {
    if(x == 0) {
        return 1;
    }else {
        return f(x-1) * x;
    }
}
  • 然后利用三目运算符,改成一行代码,得到:
int f(int x) {
    return x ? f(x-1) * x : 1;
}

在这里插入图片描述

  • 通过这段代码,我了解了 递推公式 和 递归调用。

算法三


【例题3】现在有一个 n ( n ≤ 10000 ) n(n \le 10000) n(n10000) 个元素的数组 a [ i ] a[i] a[i],但是我们已知的是前 i i i 个元素的和 f [ i ] ( 1 ≤ i ≤ n , 1 ≤ f [ i ] ≤ 100000 ) f_[i](1 \le i \le n, 1 \le f[i] \le 100000) f[i](1in,1f[i]100000),然后给出 Q ( Q ≤ 1000000 ) Q(Q \le 1000000) Q(Q1000000) 次询问 ( l , r ) ( 1 ≤ l ≤ r ≤ n ) (l, r) (1 \le l \le r \le n) (l,r)(1lrn),求 ∑ i = l r a [ i ] \sum_{i=l}^r a[i] i=lra[i]

  • 首先根据题意,得知: a [ i ] = f [ i ] − f [ i − 1 ] a[i] = f[i] - f[i-1] a[i]=f[i]f[i1]
  • 可以用一个for循环计算出所有a[i]的值。
const int maxn = 100005;
void preCalculate(int f[maxn]) {
    int a[maxn];
    for(int i = 1; i <= n; ++i) {
        a[i] = f[i] - f[i-1];
    }
}
  • 然后对于每次循环,循环统计 a [ i ] a[i] a[i] 的累加和,返回答案。
int get(int a[], int l, int r) {
    int s = 0;
    for(int i = l; i <= r; ++i) {
        s += a[i];
    }
    return s;
}

  • 事实的确如此,如果每个询问都是 1 到 n n n 的话,时间复杂度就会变成 O ( n Q ) O(nQ) O(nQ),总的数量级在 1 0 10 10^{10} 1010,比较难承受了。
  • 再仔细想想,不难发现。我们可以将要求的结果定义为函数 g ( l , r ) g(l ,r) g(l,r),则有:
  • g ( l , r ) = a [ l ] + a [ l + 1 ] + . . . + a [ r ] = ( f [ l ] − f [ l − 1 ] ) + ( f [ l + 1 ] − f [ l ] ) + . . . + ( f [ r ] − f [ r − 1 ] ) = f [ r ] − f [ l − 1 ]
    g(l,r)=a[l]+a[l+1]+...+a[r]=(f[l]f[l1])+(f[l+1]f[l])+...+(f[r]f[r1])=f[r]f[l1]
    g(l,r)=a[l]+a[l+1]+...+a[r]=(f[l]f[l1])+(f[l+1]f[l])+...+(f[r]f[r1])=f[r]f[l1]
  • f [ l ] f[l] f[l] f [ r ] f[r] f[r] 都是题目给出的,神奇!
  • 直接得到一行代码求解:
int g(int l, int r) {
    return f[r] - f[l-1];
}

  • 学姐估计是太激动,本来想夸我 “数学底子不错”,结果说成了 “数学底子不过” …… 只要她不尴尬,尴尬的就是我 ……
  • 通过这段代码,我了解了 前缀和 和 差分法。

算法四

【定义1】对于一个数 a a a,如果有数 b b b 能够整除 a a a,则称 a a a b b b 的倍数, b b b a a a 的约数。
【定义2】如果一个数 c c c 同时是数 a a a 和 数 b b b 的约数,则称 c c c a a a b b b 的公约数。
【定义3】如果 c c c a a a b b b 中最大的公约数,则称 c c c a a a b b b 的最大公约数,记为 c = g c d ( a , b ) c = gcd(a, b) c=gcd(a,b)

  • 例如,1,2,4 均为 8 和 12 的公约数,最大的公约数就是 4。
    在这里插入图片描述

  • 我可以枚举所有 a a a 的约数,然后再判断它是不是 b b b 的约数,从而找到最大的那个满足条件的约数就是答案了。算法实现如下:
int gcd(int a, int b) {
    int maxret = 1;
    for(int i = 2; i <= a; ++i) {
        if(a % i == 0 && b % i == 0)
            maxret = max(maxret, i);
    }
}
  • 这个算法的时间复杂度是 O ( n ) O(n) O(n) 的。

  • 以下这段话出自当年学姐的口中:

  首先,当 b ≠ 0 b \neq 0 b=0 时,我们令 a = k b + r a = kb + r a=kb+r,其中 k = ⌊ a b ⌋ k = \lfloor \frac a b \rfloor k=ba r = a   m o d   b r = a \ mod \ b r=a mod b,并且满足 ( 0 ≤ r < b ) (0 \le r < b) (0r<b),当一个数 c c c,是 a a a 的约数,也是 b b b 的约数,则必然也是 a − k b a-kb akb 的约数,即 r r r 的约数。自然 a a a b b b 的公约数 也就是 b b b r r r 的公约数。
  所以, a a a b b b 的最大公约数 = = = b b b r r r 的最大公约数。表示为: g c d ( a , b ) = g c d ( b , a   m o d   b ) gcd(a, b) = gcd(b, a \ mod \ b) gcd(a,b)=gcd(b,a mod b)

  • 但是我们的假设是建立在 b ≠ 0 b \neq 0 b=0 上的,而 b = 0 b=0 b=0 的情况,答案显然就是 a a a 了。于是对于上面的 g c d gcd gcd 函数,我们可以表示成如下递归式:
  • g c d ( a , b ) = { a b = 0 g c d ( b , a   m o d   b ) b ≠ 0 gcd(a,b) =
    {ab=0gcd(b,a mod b)b0
    gcd(a,b)={agcd(b,a mod b)b=0b=0
  • 写成 c++ 代码就是:
int gcd(int a, int b) {
    return !b ? a : gcd(b, a % b);
}
  • 这里 !是 c/c++ 中的表达式取非的意思,即 真变假,假变真,且 c/c++ 中 0 为假,非0为真;%是取模,即 m o d mod mod 的程序语法。
  • 学姐真是太装逼牛逼了!又是一行代码让我学到了这么多知识,满满的干货!

在这里插入图片描述

  • 这时候,天上掉下来一座长城!
  • 果然是说曹操,曹操就到啊!
  • 难道,这个梦是在暗示我赶紧把学姐的算法学完吗???


六、梦不到,被吹散

  • 充满求知欲的我不甘心,还想继续睡。可再也梦不到学姐了。
    在这里插入图片描述
  • 我逐渐陷入沉思,等我反应过来的时候,已然到了晚上,今天的课又没去上!
  • 我不甘心啊! 学姐快把算法传授给我吧!

七、往事如风

  • 十年后的今天再回首,恍如隔世,学姐当时托梦,的确让我受益匪浅,这也是我后来誓要写成❤️《夜深人静写算法》❤️系列最大的动力。这个系列目前还在紧锣密鼓的更新中,适合高中IOer,大学Acmer,以及职场的有志青年学习算法之用。
  • 至于学姐后来到底有没有托梦于我,我会在 《❤️学姐说她用 8 行代码写了 8 个算法(下)❤️》 里继续更新,如果有想帮我想剧情的,亦或是对学姐的钦慕之情,都可以在评论区留言告诉我, 学姐是我的,也是你的。

  • 本文通过一些简单的算法,对 c/c++ 的语言性质进行了一些温故而知新,希望对各位初学者有所帮助。
  • 以下是本文涉及到的知识点,最后再进行一个总结归纳。

1、知识点回顾

知识点难度
整除★☆☆☆☆
倍数★☆☆☆☆
约数★☆☆☆☆
前缀和★★★☆☆
差分法★★★☆☆
递推公式★★★☆☆
递归调用★★★★★
整数取模★☆☆☆☆
最大公约数★★★☆☆
表达式取反★☆☆☆☆
整数的溢出★★☆☆☆
三目运算符★★☆☆☆
变量的作用域★★☆☆☆
整数的补码表示★★☆☆☆
表达式真值的省略写法★☆☆☆☆

2、温故而知新

// 算法1:求和公式
int sum(int n) {
    return (n%2) ? (n+1)/2*n : n/2*(n+1);
}
// 算法2:递归求阶乘
int f(int x) {
    return x ? f(x-1) * x : 1;
}
// 算法3:差分法求部分和
int g(int l, int r) {
    return f[r] - f[l-1];
}
// 算法4:递归求最大公约数
int gcd(int a, int b) {
    return !b ? a : gcd(b, a % b);
}

原文链接:https://blog.csdn.net/WhereIsHeroFrom/article/details/117596791



所属网站分类: 技术文章 > 博客

作者:动漫魔动

链接:http://www.phpheidong.com/blog/article/89446/27438034c58879f441db/

来源:php黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

16 0
收藏该文
已收藏

评论内容:(最多支持255个字符)