QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 4482|回复: 1
打印 上一主题 下一主题

❤️两万字《算法 + 数据结构》全套路线❤️(建议收藏)

[复制链接]
字体大小: 正常 放大
杨利霞        

5273

主题

82

听众

17万

积分

  • TA的每日心情
    开心
    2021-8-11 17:59
  • 签到天数: 17 天

    [LV.4]偶尔看看III

    网络挑战赛参赛者

    网络挑战赛参赛者

    自我介绍
    本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。

    群组: 2018美赛大象算法课程

    群组: 2018美赛护航培训课程

    群组: 2019年 数学中国站长建

    群组: 2019年数据分析师课程

    群组: 2018年大象老师国赛优

    跳转到指定楼层
    1#
    发表于 2021-7-8 15:06 |只看该作者 |倒序浏览
    |招呼Ta 关注Ta
    / q, h/ [0 w3 R  y/ t+ h, y
    ❤️两万字《算法 + 数据结构》全套路线❤️(建议收藏)1 m+ _, I5 v5 B9 I( P: s

    ' S* d3 \7 L- @, z. r/ T' s4 m前言
    1 i3 @* |3 u& e4 t  所谓活到老,学到老,虽然我感觉自己已经学了很多算法了,但是昨天熬夜整理完以后发现,自己还是个弟弟,实在忍不住了,打算把 算法学习路线 发出来,我把整个算法学习的阶段总结成了五个步骤,分别为: 基础语法学习(重要)、语法配套练习、数据结构、算法入门、算法进阶。本文梳理了这五个大项的思维导图,在下文会有详细介绍。
    + v# L' f" x1 J, r  希望各位能够找到自己的定位,通过自己的努力在算法这条路上越走越远。
    6 p# X! x+ w- R, D  刚开始切勿心浮气躁,千万不要给自己立 flag,说一定要把这么多东西都学会。就算你的精力旺盛,日夜操劳,时间也是有限的。所以,首先是明确我们要做什么,然后制定好一个合理的 目标 ,再一点一点将要学习的内容逐步付诸实践才是最重要的。
    8 K- @/ [& U7 `  `- o  每日一篇C语言打卡,目前更新到:光天化日学C语言(20)- 赋值运算符与赋值表达式 | 让代码变得更加简介(建议收藏)。; H0 q+ m' W0 y

    9 c' f# ^, `5 Y' ]
    / g* l; G1 T/ \* Z. I  [& J9 n
    & B+ z- t/ F- N% j" C9 X" |8 L
    9 `) ^9 I0 M  s0 E8 S7 d2 S% ^
    # Q( b1 E! H3 C7 }

    % W" i& U- L7 M
    / |- P, z& v" D4 Q  @+ Z  i  p% I

    2 e: O& ?1 d# i; E+ {图片较大,文章中有拆解,需要原图可以留言找我要哈5 r$ l7 u; w' Q8 Z) _
    1、基础语法学习4 u/ n; n% _" k
    算法是以编程语言为基础的,所以选择一门编程语言来学习是必须的。
    / W9 U" E- A% _. S  N因为作者本身是C/C++技术栈的,所以就拿C语言来举例子吧。如果是 Java、Python 技术栈,可以跳过 C语言相关的内容。这一小节,先给出学习路线图,然后我再来讲,每部分应该如何去学。
    1 k* k. o, L' B: K2 J
      C  o2 l( M3 O# n9 j& d: f

    ( Q0 Z( f9 z; Z; o  W, z& S" R5 c7 J! `1 a
    % d2 I4 t2 Y. q1 f& W8 F
    1)HelloWorld
    " d7 X1 Z! O4 Z% e2 _无论是 Java、Python、C/C++,想要上手一门语言,第一步一定是 HelloWorld,先不要急着去配环境。如果环境配了几个小时,可能一开始的雄心壮志就被配环境的过程消磨殆尽,更加不要谈日后的丰功伟业了。0 I! }* ^7 D7 w0 A$ i! m
    2)让自己产生兴趣
    ; I* g. e, {  C' v- C# u" U) b3 H所以,我们需要让这件事情从一开始就变得 有趣,这样才能坚持下去。比如找一个相对较为有趣的教程,这里我会推荐这个:《光天化日学C语言》。听名字就比较搞笑,可能作者本身也不是什么正经人,哈哈哈!虽然不能作为一个严谨的教程去学,起码可以对搞笑的内容先产生兴趣。从而对于语言本身有学习下去的动力。
    9 U! c& T/ N. L刚才提到的这个系列,可以先收藏起来。回头再去看,它讲述的是 对白式 的 C语言教学,从最简单的输出 HelloWorld 这个字符串开始讲起,逐渐让读者产生对C语言的兴趣。这个系列的作者是前 WorldFinal 退役选手,一直致力于 将困难的问题讲明白 。我看了他的大部分教程,基本都能一遍看懂。算了,不装了,摊牌了,因为我就是这个作者。
    * h: w6 }8 M( o+ R- z( _" a3 _& N3)目录是精髓8 i3 C- e' W9 x
    然后,我们大致看下你选择的教程的前几个章节,那些标题是否有你认知以外的名词出现,比如以这个思维导图为例,前几个章节为:
    ; N- ~+ G0 l. o) M" v1、第一个C语言程序
    8 s2 ~! l+ Z- V* _( n. o. D2、搭建本地环境5 n" C6 g. C  J# Y: O% D. q
    3、变量6 {' @: P" h, V/ |& Y
    4、标准输出5 Y& c9 D9 \7 t& R; ^2 o$ q; O7 _
    5、标准输入
    . q# ~) Q4 R' t- I/ H  Y: S; L- b! u6、进制转换入门
    7 L" F$ Q6 h. V* l7、ASCII字符) S- d. H5 R2 z( v* X4 w6 F3 E
    8、常量: [& z4 d- m( v) L7 b2 c: D: }; }

      X$ ^' f& _/ S, n1 j! v% f
    8 A- s' c7 o* S9 m$ Q5 {
    如果你觉得这些名词中有 3 / 4 以上是没有什么概念的。那么,可能需要补齐一些数学、计算机方面的基础知识。反之,我们就可以继续下一步了。
    ; z4 N8 ^7 S+ }4)习惯思考并爱上它! s5 k9 b# ~, Y# o
    只要对一件事情养成习惯以后,你就会发现,再难的事情,都只是一点一点积累的过程。重要的是,每天学习的过程一定要吃透,养成主动思考的好习惯。因为,越到后面肯定是越难的,如果前期不养成习惯,后面很可能心有余而力不足。7 D- E3 E# K( P) ^1 f3 s" @- I
    就像刷题,一旦不会做就去找解题报告,最后就养成了看解题报告才会做题的习惯。当然这也是一种习惯,只不过不是一种好习惯罢了。
    2 X6 o* g; o6 B) E5)实践是检验真理的唯一标准% h' B/ q4 b. _) Q4 n5 Y5 x1 Y2 z
    光看教程肯定是不行的,写代码肯定还是要动手的,因为有些语法你看一遍,必定忘记。但是写了几遍,永世难忘。这或许就是写代码的魅力所在吧。
    , x/ w: K' z4 ?, ?% u. v( S所以,记得多写代码实践哟 (^U^)ノ~YO+ }7 o5 \' }5 b; v
    6)坚持其实并没有那么难
    + X: x1 d; v4 O& t+ ]9 y- b每天把教程上的内容,自己在键盘上敲一遍,坚持一天,两天,三天。你会发现,第四天就变成了习惯。所以坚持就是今天做了这件事情,明天继续做。
    ; }+ A/ d' I9 a) D6 d% `7)适当给予正反馈
    ; V. J# q; O$ Y1 b0 X( @然而,就算再有趣的教程,看多了都会乏味,这是人性决定的,你我都逃不了。能够让你坚持下去的只有你自己,这时候,适当给予自己一些正反馈就显得尤为重要。比如,可以用一张表格将自己的学习计划记录下来,然后每天都去分析一下自己的数据。: `3 J. H  m' F  }3 l+ U& U
    当然,你也可以和我一样,创建一个博客,然后每天更新博文,就算没有内容,也坚持日更,久而久之,你会发现,下笔如有神,键盘任我行!更新的内容,可以是自己的学习笔记,心路历程 等等。) b6 }8 O5 |, s: t, U
    看着每天的粉丝量呈指数级增长,这是全网对你的认可,应该没有什么会是比这个更好的正反馈了。( o8 F  q( v3 U; c
    8)学习需要有仪式感$ o+ Q' Z$ L$ N9 |0 [
    那么,至此,不知道屏幕前的你感想如何,反正正在打字的我已经激情澎湃了。已经全然忘记这一章是要讲C语言基础的了!
    9 \1 N7 u, j9 E1 M' h2 x" C3 B) n, A  C介于篇幅,我会把C语言基础的内容,放在这个专栏 《光天化日学C语言》 里面去讲,一天更新一篇,对啊,既然说了要坚持,要养成习惯,我当然也要做到啦~如果你学到了哪一章,可以在评论区评论 “打卡” ,也算是一种全网见证嘛!9 Y1 n# B+ e, ?( ~/ I- A
    我也很希望大家的学习速度能够超越我的更新速度。4 q& f8 y7 v) l2 d# O1 q/ z
    2、语法配套练习
    2 _8 J0 {4 g6 t. C1 J学习的过程中,做题当然也是免不了的,还是应征那句话:实践是检验真理的唯一标准。
    $ b  `2 x0 v  _$ M. k4 T而这里的题库,是我花了大量时间,搜罗了网上各大C语言教程里的例题,总结出来的思维导图,可以先大致看一眼:
    + T7 _* K6 {$ ~  r% z; h3 Z7 r8 T- y# Z% ?) A
    8 e9 F2 c. O+ {4 s( p+ z
    6 c5 T) M' v) x
    ! i" k; k0 P1 L  H" _! l3 g1 P
    从数学基础、输入输出、数据类型、循环、数组、指针、函数、位运算、结构体、排序 等几个方面,总结出的具有概括性的例题 100 道 《C语言入门100例》,目前还在更新中。! R% R( j9 u% y
    这里可以列举几个例子:/ j0 D) [8 e5 i7 \5 v
    1、例题1:交换变量的值( \( o' T; Q& n0 N1 }- m
    一、题目描述
    7 r& l7 `6 x# A. c9 d' }) s0 x, x7 A  循环输入,每输入两个数 a aa 和 b bb,交换两者的值后输出 a aa 和 b bb。当没有任何输入时,结束程序。
    + @/ h2 f2 T* @+ I; D- w- }* {* l: o3 I

    & w% |& t" f/ m3 M! v4 |2 ]) l: ?/ m" O( @/ u
    5 J8 X9 X! Y$ U/ _' o4 z
    二、解题思路6 P# Z, J3 D3 N$ |% h$ R
    难度:🔴⚪⚪⚪⚪5 \! n$ f' q) U( O0 m! S

    : W" e2 Z( w9 X9 T- d( V

    0 p- V0 E( i) D1 j9 O, ]这个题的核心是考察如何交换两个变量的值,不像 python,我们可以直接写出下面这样的代码就实现了变量的交换。
    4 A  X, y) s* I5 ua, b = b, a
    1 x* n5 e# h" n3 E3 M+ e% R1  [! b& W' Y7 Y7 t' ~
    在C语言里,这个语法是错误的。9 n( b5 G6 ?. W+ G5 t8 T
    我们可以这么理解,你有两个杯子 a aa 和 b bb,两个杯子里都盛满了水,现在想把两个杯子里的水交换一下,那么第一个想到的方法是什么?+ ]$ J! d$ B3 H5 [( _( _
    当然是再找来一个临时杯子:7 S4 o6 ~) G9 J1 P& e, ^
      1)先把 a aa 杯子的水倒进这个临时的杯子里;+ p3 s* i& U7 |! {( Y) U
      2)再把 b bb 杯子的水倒进 a aa 杯子里;
    . B' Q1 z; U1 i" C( x4 E% P  3)最后把临时杯子里的水倒进 b bb 杯子;
    * N" q& t; Q$ b9 A
    * Q+ [4 E8 v- F) f! M7 x

    ; }; y6 G; w% |, i这种就是临时变量法,那么当然,还有很多很多的方法,接下来就让我们来见识一下吧。- L, a% ~/ L7 @6 }& e

    8 A  L( j: r( r  T' r) g

    0 @  w6 y; U, s0 J* d三、代码详解! V! X+ o" ]/ p  e0 S
    1、正确解法1:引入临时变量3 @9 l! K4 ?! W
    #include <stdio.h>( m) b- p2 w% Y; h
    int main() {& e2 a. o/ Y6 B' D7 p$ b3 I
        int a, b, tmp;9 N& K; Z: K% u" ?7 k0 ]! L, T
            while (scanf("%d %d", &a, &b) != EOF) {* b0 s0 c, b+ ~' O+ l% I2 m
                tmp = a;   // (1)8 V5 c9 U% L) X, o7 _
                a = b;     // (2)
    * @! e* S7 M* d: p8 ~  ~            b = tmp;   // (3)
    9 H  J4 |/ f% ]& p& Z: W) l4 b! o            printf("%d %d\n", a, b);
    ) T$ O% l' X! J2 L" l        }% F7 S0 S% o, N9 m- r* m7 O; s, C" I
            return 0;
    1 U# `- W0 Z% W+ y6 M* @6 K  x}  d! J$ D5 o# U& O
    10 o6 ~1 k+ O1 ^
    2
    8 C, V4 _6 Q- g, g6 A- e) U/ v3
    , |  {3 S0 x4 Q- @1 E4
    ( S' q6 A5 t9 F# d7 L% S5+ Z" v0 c/ l6 P
    60 n5 `% \0 t/ b. X; B) M1 s
    7) i: e/ z, O3 c! m1 T
    8
    * ~, w; h9 p$ Z2 n2 K6 \9
    : {4 j9 \/ N: m3 ]0 h( q10- C. }# }8 u% ^& p; \
    115 J$ Z1 Y4 w# m$ {3 O6 A8 A
    ( 1 ) (1)(1) tmp = a;表示把 a aa 杯子的水倒进这个临时的杯子里;
    4 P& J# p8 Y# ]( 2 ) (2)(2) a = b;表示把 b bb 杯子的水倒进 a aa 杯子里;. H3 \& e: Y* N* X9 ]6 N
    ( 3 ) (3)(3) b = tmp;表示把临时杯子里的水倒进 b bb 杯子里;, ~" [" d1 ^6 ~4 d: K4 D/ D! w
    这三步,就实现了变量 a aa 和 b bb 的交换。
    ' B% p- S. j  T2 Z  v2、正确解法2:引入算术运算" v8 C& i" z, p
    #include <stdio.h>
    ! f% |1 ]  O; p8 C9 u: @int main() {
    3 L  k+ C( y1 X6 S, Q5 `0 b; `    int a, b;
    6 H) A, B4 F# ]        while (scanf("%d %d", &a, &b) != EOF) {
    , S( t9 z: P- P3 G            a = a + b;   // (1)5 g! Q" U8 X4 m, V. ^$ A; G$ e
                b = a - b;   // (2)
    / g4 p- K1 ^! s+ u6 t; y            a = a - b;   // (3)
    0 w" N0 Z8 c, u2 D: j! W8 e2 l" r            printf("%d %d\n", a, b);
    + l& x4 Y. N+ `        }
    + O4 z1 P$ M0 {# \2 k% a0 m: q# {        return 0;
    0 j% w) \9 d5 P& T0 o}+ E6 F5 o. A* G. J5 |# S6 H
    1
    ' f0 r0 _, {7 i2
      i( B% R5 w! \. W" z4 T3. K: u: R: a/ o5 }( T+ S0 E# q% e
    4
    9 I- x6 Q8 Q3 Z: r- A, w) ?: O53 ]- [$ b9 Q& N/ V
    6. [% K) P. M. ]) [
    7
    6 h# P4 F* p7 P) V, Q4 F" Q, D8
    7 d& x- t) b2 a" O8 p; i, W0 R5 _" d9$ n) ]( \5 [1 ]1 {5 n
    10
    6 R5 y6 G7 F/ V+ P6 M1 t, p11. R! M3 c$ C# s  c( B, P0 ]* a
    ( 1 ) (1)(1) a = a + b;执行完毕后,现在最新的a的值变成原先的a + b的值;
    8 T8 k! h& h' r5 u; Y. A# X) E* `. P( 2 ) (2)(2) b = a - b;执行完毕后,相当于b的值变成了a + b - b,即原先a的值;
    ! I' O2 W( ~2 N9 G* z& H% H/ O( 3 ) (3)(3) a = a - b;执行完毕后,相当于a的值变成了a + b - a,即原先b的值;( x0 z% ^* t. G' h
    从而实现了变量a和b的交换。$ x5 `* M. _( ^! S$ n% C* U
    3、正确解法3:引入异或运算
    $ r9 \! K3 f) u) b) x* V6 V, C首先,介绍一下C语言中的^符号,代表的是异或。
    8 t3 x! L/ s: Y- i二进制的异或,就是两个数转换成二进制表示后,按照位进行以下运算:
    8 r6 ]! ]% B5 n! k* Z# k左操作数        右操作数        异或结果
    4 u8 E4 p) }. I3 U6 ~6 S" W0        0        0! @  `) P  V1 D- A/ \7 Y/ `
    1        1        08 C! L9 }: K3 X7 d% O% a2 A
    0        1        1+ t# i" Q! J3 |" P4 l6 b) H2 i! w
    1        0        15 c" c5 J6 M6 ]6 a: p
    也就是对于 0 和 1,相同的数异或为 0,不同的数异或为 1。% E' x) }: L& R% _: }6 D* P
    这样就有了三个比较清晰的性质:/ u5 i0 m& W5 r2 H# Y
    1)两个相同的十进制数异或的结果一定位零。
    . s, A7 ~' y3 }! F1 ^2)任何一个数和 0 的异或结果一定是它本身。, K( b; _) f7 U$ v. L. J% R3 r: F
    3)异或运算满足结合律和交换律。" J2 X0 Q. C9 B/ I: B/ D& e
    #include <stdio.h>
    " P( N5 V- S: t3 m: Rint main() {  _8 [( O9 d- e2 m
        int a, b;
    0 @/ i0 Y4 ^: Z& G        while (scanf("%d %d", &a, &b) != EOF) {: E5 p# |# I9 e1 `9 j" `- \+ h
                a = a ^ b;   // (1)
    $ l2 W% q# \& @( N1 t9 w# N9 G3 k            b = a ^ b;   // (2)5 c  i! p( S+ P% @4 i+ w
                a = a ^ b;   // (3)
    3 c6 m" l5 P) L0 a9 Q! }% [2 {            printf("%d %d\n", a, b);
    5 j* \& E1 H3 l0 r" Z& U) ]) N        }
    & r( l/ Q& J9 `" T8 X+ E" o        return 0;  ?$ j7 v+ a/ ?: r) d
    }4 g( ~7 ]" v* K
    1
    7 M0 |! p4 a& Z' O: {) T2
    " ~: [6 m3 V( K" @" P" a( M* t39 t9 M  `! i" _3 x0 o- ], @8 t' e1 R
    4' ^5 F# ~8 k' ~% p, [' \5 C7 ]6 @
    5% C% _7 G" ^- P2 W% I8 G
    6% d% C/ N- `+ \0 P- `0 l4 [% E, o
    7" f# k9 G: h; J, t# S  A$ f
    88 X! O+ S' A9 M' Y$ L
    99 P' M+ D: m4 O9 @3 S1 Y( A
    10
    % ?( D2 B  L! K) s5 e$ J" J0 e11$ h7 u! \2 m9 I. T, j! a* X
    我们直接来看 ( 1 ) (1)(1) 和 ( 2 ) (2)(2) 这两句话,相当于b等于a ^ b ^ b,根据异或的几个性质,我们知道,这时候的b的值已经变成原先a的值了。8 ^6 W, ^7 |- C- M1 X$ G2 m
    而再来看最后一句话,相当于a等于a ^ b ^ a,还是根据异或的几个性质,这时候,a的值已经变成了原先b的值。( Y$ p5 [) O7 E% m: C  I
    从而实现了变量a和b的交换。8 T: F- l# g1 O, z6 Z
    : ?( ?: U& R6 m

    # H9 ^7 z0 R6 y% Q9 X4、正确解法4:奇淫技巧
    - Z$ Z; G" y* o# I+ D) i当然,由于这个题目问的是交换变量后的输出,所以它是没办法知道我程序中是否真的进行了交换,所以可以干一些神奇的事情。比如这么写:
    " X6 C) B2 z+ Y' H#include <stdio.h>4 Q, [: \. X/ M* S8 s
    int main() {
    + W7 Y; ?4 b  S* w7 G4 h# x8 R/ X    int a, b;+ [- ^( u( X4 Q- k6 k
            while (scanf("%d %d", &a, &b) != EOF) {
    ! |; M) X9 ]6 t            printf("%d %d\n", b, a);+ L5 I6 I$ l# t$ i0 x
            }: e' P- c5 Z' Z  i- _  t/ l# w2 ~1 n
            return 0;
    ( `" ?. e1 ?9 u# `* t$ V}: c# \1 c* ?. c2 w2 t- Y4 H6 c" J
    1" X5 l/ t6 X7 u2 z3 ?. p
    2- A% I5 C5 G$ k' {
    3
    - [! `# D% D9 T, b4- ?+ C! p' U$ {( k" b7 W
    5. B+ t* L0 W8 b7 P# h
    6
    3 n! j5 N+ Y0 T% C7! p3 [+ C- g( Y& j
    88 b8 c3 R, Z4 t) V# C
    你学废了吗 &#129315;?8 t# Y' j0 D' ?# z* B, ?
    2、例题2:整数溢出
    ; Y$ B( F' D  ~* Y8 [一、题目描述9 R( i/ |+ y% W" a4 \& I- `" Q+ t
      先输入一个 t ( t ≤ 100 ) t (t \le 100)t(t≤100),然后输入 t tt 组数据。每组输入为 4 个正整数 a , b , c , d ( 0 ≤ a , b , c , d ≤ 2 62 ) a,b,c,d(0 \le a,b,c,d \le 2^{62})a,b,c,d(0≤a,b,c,d≤2
    6 s; J. ?1 _7 B1 f# p3 S624 c" w* n1 Y* c" R$ t2 j
    ),输出 a + b + c + d a+b+c+da+b+c+d 的值。
    : o9 z; e6 ^. _0 V  m/ i
    6 I' h  j4 n6 M' U- K
    / S+ h$ R* L- P6 O2 v  \
    二、解题思路) [' g$ H3 ?; s$ F/ v/ q8 a3 ?
    难度:&#128308;&#128308;⚪⚪⚪8 w& Q6 M; G# j% t$ D: P
    7 p% W5 K- Y5 s9 T1 o3 z1 u
    + z  k0 l8 p  u
    这个问题考察的是对补码的理解。& k; P2 F$ K. C- G; w' z
    仔细观察题目给出的四个数的范围:[ 0 , 2 62 ] [0, 2^{62}][0,2
    8 c2 e8 o9 @  D  Z62
    0 R* y, t% k9 h% d( D ],这四个数加起来的和最大值为 2 64 2^{64}2 3 s& ~4 c" \- h
    64
    / t2 a/ T/ E" p! a$ n1 B( D 。而C语言中,long long的最大值为:2 63 − 1 2^{63}-12
    ; ]0 g& A! [& U5 m# V& P63
    8 I, [1 o5 r4 l0 b1 [0 C −1,就算是unsigned long long,最大值也只有2 64 − 1 2^{64}-12 7 v. `) p" X: x' ^+ }6 V
    64
    + T4 V- o" V; r' P( d- u; i −1。
    4 b; x+ I3 W& C" `, Z但是我们发现,只有当四个数都取得最大值 2 62 2^{62}2 ; s  t! ~6 R: M6 m: Y) m3 j
    62
    : t) x" R. u: g5 R6 o  时,结果才为 2 64 2^{64}2
    + ]0 |# ^  A: _64/ u- k$ s) `% _7 u3 I$ L, m: y
    ,所以可以对这一种情况进行特殊判断,具体参考代码详解。8 e7 ]! E# a+ R& q: f8 e' t
    三、代码详解* S1 }% [) Y5 E8 C) K& R
    #include <stdio.h>2 \( x* S+ b* k7 M1 e
    typedef unsigned long long ull;                           // (1)* c+ t8 d0 @, D6 V4 B2 w" m! @
    const ull MAX = (((ull)1)<<62);                           // (2)
    8 G  p$ i% v& T
    4 G: L3 x1 x: }- E2 S
    + w$ n  I# u$ S8 K# N' S8 o6 ]1 ?
    int main() {: J4 i+ o: s  q
            int t;' N/ ?, R; N, e  Y
            ull a, b, c, d;
    5 J: i- ?- K' u* u* |( o* G        scanf("%d", &t);. `# U. v1 T3 R5 L% v2 [- ~
            while (t--) {
    + t1 F+ E) E8 @6 P) x3 x# C$ J  x                scanf("%llu %llu %llu %llu", &a, &b, &c, &d);     // (3)& a, b( `8 Z( J/ e5 [1 Q7 \
                    if (a == MAX && b == MAX && c == MAX && d == MAX) // (4)* S/ i8 J& `# h
                            printf("18446744073709551616\n");             // (5)
    ) x6 W) v9 O1 `$ E2 x                else9 c8 ]: G" K% H
                            printf("%llu\n", a + b + c + d);              // (6)
    / O2 x# A1 [3 w7 L2 S        }
    * G: n# @' V) w+ M" \9 F; N        return 0;5 p4 F8 M& ?: a' N2 _$ m
    }
    9 I7 @7 w7 g- K) I, F3 H1
    * s  Y! y. P# C$ o2+ h2 v% o5 |4 [' D. K
    3
    4 d" m$ T2 \( V1 A5 m. x' h2 _% J4 m4
    4 Q4 n: H# q" P  H/ }& q- k! H5: [. V; h: P9 i5 g2 M' P* `: Q
    6! Z# K4 ?* y: C  E
    7
    # h! E& H2 R5 [! @0 Y8' b/ H! @$ S: a
    9
    % o8 k! k0 L" N10
    ; l  j* Z$ b' M5 I1 [; [" {11# K4 n6 Y! c* k
    127 w9 U. m. s( ]1 g1 t
    13; l: H: c9 `6 l: E2 J  k$ i
    14( N! j5 g$ q5 A
    15
    ! V" ^/ `9 a+ `# h. ~16
    5 X7 Q, c4 g  ~7 a17
    : B0 g% L9 ]* R& y4 Q+ m9 y( 1 ) (1)(1) 由于这题数据量较大,所有数据都需要用64位无符号整型。ull作为unsigned long long的别名;
    9 g; r6 u# S% c* j( 2 ) (2)(2) 用常量MAX表示 2 62 2^{62}2
    / w. d5 d3 y8 E- G( m: E62) u/ }& U8 w/ ~1 y9 K& o
    ,这里采用左移运算符直接实现 2 22 是幂运算;
    : u2 a( m" `' j/ ]' L4 G$ ~数学        C语言0 h3 ~8 h  M7 f& E  R, l
    2 n 2^n2
    & W. j8 Q* e! `0 a& T' x  o0 {n
    * v, I! B0 j! \9 S7 y* h         1<<n
    4 {# G5 U: F+ G* Z- B! ]+ i. [需要注意的是,由于 1 是int类型,所以需要对 1 进行强制转换。(ull)1等价于(unsigned long long)1;
    7 `" F+ C" f: j" q/ q* {4 e( 3 ) (3)(3) %llu是无符号64位整型的输入方式;
    : b$ |- j' @" k% I$ W! @' J& q( 4 ) (4)(4) 这里是对所有数都等于最大值的特殊判断,&&运算符的优先级低于==,所以这里不加括号也没事;" x8 r2 O. d) }- h7 ^4 q1 x4 ]5 ^  G
    ( 5 ) (5)(5) 由于 2 64 2^{64}2
    % C4 Q2 r( A- B( p* t  D1 }' j& e. e9 o64
    1 N* `4 ?0 F4 G8 k# Y+ U  是无法用数字的形式输出的,所以我们提前计算机算好以后,用字符串的形式进行输出;7 l# Q2 @  C4 m6 q
    ( 6 ) (6)(6) 其它情况都在 [ 0 , 2 64 − 1 ] [0, 2^{64}-1][0,2
      q9 q5 r9 `4 ^64
      q& W3 j7 G% e/ m −1] 范围内,直接相加输出即可。+ I6 q8 s+ ?% v% Q
    由于这个专栏是付费专栏,可能对学生党不是很友好,所以作者经过再三思考,打算放出 300 张 一折优惠券, 先到先得。只要拿这个图片来找作者即可享受,仅限前 300 名。" ~3 V9 Y- l. _- g  v7 G2 `
    为了适当提高一定门槛,你至少需要学会如何下载图片或者截图并且发送到微信里 &#129315;。
    ' s& b: c% c7 U3 B& G8 u
    4 f9 d- G) i( d' \; q
    # I! j$ a% a8 d: j5 t; m
    3、数据结构; D) t3 i1 y5 g, a) h' {5 _
    《C语言入门100例》上的例题,如果能理解前面 25 道,那基本C语言的学习就可以告一段落了,接下来就要开始我们的数据结构的学习了。
    / N- [$ _7 L% P1、什么是数据结构
    * X7 E) ^2 Y, B; q你可能听说过 数组、链表、队列、栈、堆、二叉树、图,没错,这些都是数据结构,但是你要问我什么是数据结构,我突然就一脸懵逼了。
    " w2 t# \* v6 _# n* j6 t1 p如果一定要给出一个官方的解释,那么它就是:2 o9 K4 ~$ z6 Q+ i8 T
    计算机存储、组织数据的方式。相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。往往同高效的检索算法和索引技术有关。
    $ T2 c) Z4 q* S" M% Z: B. m+ S: X( p, ^2 N( O) w. n8 Q" ^# m

    4 o5 I2 c8 n0 j* v  t3 @是不是还不如说它是堆,是栈,是队列呢?
    4 p9 u: H9 k. \- C  C7 _是这样的,我们学习的过程中,跳过一些不必要的概念,能够节省我们更多的时间,从而达到更好的效果,当你还在理解数据结构是什么的时候,可能人家已经知道了栈有哪些操作了。
    $ B3 R2 W7 G$ v/ A2 t0 V2、数据结构和算法的关系" L: n5 t8 s) h$ K4 B' l; @! b% j9 F, p
    很多同学搞不明白,数据结构与算法有哪些千丝万缕的关系?甚至有些同学以为算法里本身就包含了数据结构。
    0 n" ?9 Z, k; U( A" r& J数据结构主要讲解数据的组织形式,比如链表,堆,栈,队列。
    1 N' A9 A! a4 K7 f& o( e! I3 \) j. P而算法,则注重的是思想,比如链表的元素怎么插入、删除、查找?堆的元素怎么弹出来的?栈为什么是先进后出?队列又为什么是先进先出?
    9 Q* C- Z9 t  s7 D* F9 s' n6 O' x8 N讲得直白一点,数据结构是有实体的,算法是虚拟的;数据结构是物质上的,算法是精神上的。当然,物质和精神 缺一不可。
    + L# w: N' z4 a6 J6 \& W3、数据结构概览
    : A3 G5 _" ^6 I周末花了一个下午整理的思维导图,数据结构:8 p. Y. n1 i# E- [' v
    7 W8 P# ~; ?4 L! a4 m

    * m* P& o( _6 b( u5 Z& b常用的一些数据结构,各自有各自的优缺点,总结如下:
    0 U; ^  Y3 W. Y7 t7 S6 U4 ?" N: ba、数组9 r5 p7 x( p; x7 W5 I# o
    内存结构:内存空间连续) @: v8 M4 \! l0 J5 Q" d
    实现难度:简单
    9 g) v; e+ O- X下标访问:支持
    3 }5 A) M6 f3 C! c# i. j分类:静态数组、动态数组
    / W, K, z1 T! D7 C5 `插入时间复杂度:O ( n ) O(n)O(n)
    ; k( `7 ]; G4 R* T7 h; l查找时间复杂度:O ( n ) O(n)O(n)
    ; I% w9 D% ?/ }2 W4 ]删除时间复杂度:O ( n ) O(n)O(n)& }5 R* H" ~, O/ f2 ]1 c1 o: ~

    - R. N; h6 i9 V: n

    ( a2 W* N7 r# u% F8 Db、字符串  t2 u, \; z' |8 x
    内存结构:内存空间连续,类似字符数组
    4 N/ _, ]9 v' t& \" \+ s实现难度:简单,一般系统会提供一些方便的字符串操作函数9 v, D, a& U! w$ h6 C
    下标访问:支持
    % |' y# \, L3 P5 M" o6 z0 O$ |插入时间复杂度:O ( n ) O(n)O(n)
    1 C8 k! u4 b4 C( \: B$ r, s4 c( q( j查找时间复杂度:O ( n ) O(n)O(n)
    * D$ u8 x# I' f  G$ O删除时间复杂度:O ( n ) O(n)O(n)$ A5 t. r& X* B( N2 i; ]6 N# ~! o
    2 M! b6 @; y  h: y" T/ e
    * R  k: ~! c. s$ C2 _& r" i9 D4 U
    c、链表0 U1 z/ h5 k& S; u4 t5 \; p1 \# f' ~% \/ {
    内存结构:内存空间连续不连续,看具体实现/ P5 B6 x. _. K4 `" h
    实现难度:一般$ x) h2 j# v9 }, f+ r3 z) A
    下标访问:不支持# W! E1 K6 f. ~
    分类:单向链表、双向链表、循环链表、DancingLinks, K! y$ i2 d- c0 G5 s
    插入时间复杂度:O ( 1 ) O(1)O(1)
      G4 P+ Z% t7 [" d7 y查找时间复杂度:O ( n ) O(n)O(n)7 a8 P& u4 f, z- ~$ X+ |6 @, C
    删除时间复杂度:O ( 1 ) O(1)O(1)
    6 W6 v6 b0 ]8 G. o4 u+ H- ^9 h: S
    3 ~5 A$ Y/ c& R; r$ l; S
    * |% E1 @: j$ e" x1 w8 N
    d、哈希表
    ; z) A1 R- y% K内存结构:哈希表本身连续,但是衍生出来的结点逻辑上不连续' V% {1 u5 `  A* {4 z
    实现难度:一般
    * w9 F! T9 B- ^3 D& n2 T. k+ v下标访问:不支持
    2 E0 i2 O8 f2 Z% }3 {: S, |分类:正数哈希、字符串哈希、滚动哈希
    4 e! q* v! P0 z& K( F插入时间复杂度:O ( 1 ) O(1)O(1)
    ; S- |  c' D0 V% m- B查找时间复杂度:O ( 1 ) O(1)O(1)
    : B0 P" l1 C6 n/ a: D  c' @删除时间复杂度:O ( 1 ) O(1)O(1)9 Y. d3 B# a6 Y! ?4 w
    5 k) U. M  W/ k+ Y. E' x
    3 Z; t3 c2 X0 v7 u( w3 p1 Q
    e、队列
    0 f: a5 g3 `* W* \4 \: S7 R. \内存结构:看用数组实现,还是链表实现
    2 b: {. ~, Z  P7 v! R% T2 p实现难度:一般
    2 S/ `2 v1 h" K* w3 x; z下标访问:不支持- S2 r4 |! x& f
    分类:FIFO、单调队列、双端队列5 y' o2 B4 e8 O( ~
    插入时间复杂度:O ( 1 ) O(1)O(1): u1 C+ u3 e4 E
    查找时间复杂度:理论上不支持9 H! {1 ]. ?; s- \& m! }- f
    删除时间复杂度:O ( 1 ) O(1)O(1)6 z* F9 A/ n3 _: {1 E

    : [6 P# `4 @, N

    ; J8 S, l4 D$ E* d" t! Z* }" |f、栈# g" Y- A$ j8 v6 q& }' d% [
    内存结构:看用数组实现,还是链表实现1 g- k* f' i9 z
    实现难度:一般  x! z% k" E" C. f. d
    下标访问:不支持
    * U+ @  y+ B( j2 W: O; q分类:FILO、单调栈
    + @) B# t2 U. H插入时间复杂度:O ( 1 ) O(1)O(1)' y. t  c2 F0 M; @' U% l
    查找时间复杂度:理论上不支持
    ; v( T+ `0 Y: X: |删除时间复杂度:O ( 1 ) O(1)O(1)
    * W( g: [1 [/ _/ @
    ; s# h1 H2 V1 E

    # L2 |% {4 u+ wg、树
    , u# X- M& \( Q, |; h* W3 k内存结构:内存结构一般不连续,但是有时候实现的时候,为了方便,一般是物理连续,逻辑不连续  C' b( B; }0 w; e% y2 y5 U
    实现难度:较难! W7 _2 Z8 I- D* }" C* Y: A
    下标访问:不支持/ I* U: Y9 K! p* D
    分类:二叉树 和 多叉树! c2 O0 }8 J9 e0 Z
    插入时间复杂度:看情况而定
      y7 }2 t. w# \4 g, f查找时间复杂度:理论上 O ( l o g 2 n ) O(log_2n)O(log
    1 q0 K' F0 L( {! n% v2
    * Q1 L# T1 ?! ]  M/ ?- [​       
    ( C& A5 X- u6 O" m' Z n)
      g9 v1 p; B) R2 z删除时间复杂度:看情况而定7 t' h  q6 I0 ]# B8 r4 k' C& t
    + c- n3 k8 P! |
    % }9 v/ R# ]1 ]- C
    1、二叉树, d" s( q( L8 c& G5 o* L
    二叉树的种类较多,比如:二叉搜索树、平衡树。平衡树又可以分为 AVL 树、红黑树、线段树、堆。最平衡的树莫过于满二叉树了。6 ?( }) N, }5 i! Z" ?2 h' m% i, v
    其中,堆也是一种二叉树,也就是我们常说的优先队列。
    6 Q0 u/ `: I3 P- ?" A/ v8 {4 h* E2、多叉树" w: P# a0 q+ @$ ]/ ^# ^
    B树和B+树是多叉树,当然我们平时学到的并查集其实也是个多叉树,更加严谨一点,应该称之为森林。) K+ w( Y6 D# ^- p+ u3 r& X6 {! y
    h、图
    # ^$ A/ v& o6 }% b5 L, t/ q+ l内存结构:不一定  P# p/ @. H4 q: G
    实现难度:难& `! p; l5 ?# \, ]( m
    下标访问:不支持
    # y- |: f" l. @. S分类:有向图、无向图
    4 G" _8 V5 @: U$ a插入时间复杂度:根据算法而定$ U# G+ E5 E/ G4 k7 f; c/ p
    查找时间复杂度:根据算法而定2 }# F9 [% D, m* y, N" O9 b9 B& v
    删除时间复杂度:根据算法而定
    $ T) L1 g3 e" Q7 z9 i- _, o- M, K/ q/ }! E0 l$ P

    1 \) Q2 v& _0 |6 M! z1、图的概念" F" T$ F+ k* p% I2 x9 n& i
    在讲解最短路问题之前,首先需要介绍一下计算机中图(图论)的概念,如下:( |# r: d8 ?: w0 @
    图 G GG 是一个有序二元组 ( V , E ) (V,E)(V,E),其中 V VV 称为顶点集合,E EE 称为边集合,E EE 与 V VV 不相交。顶点集合的元素被称为顶点,边集合的元素被称为边。
    # p8 v# |! p$ D对于无权图,边由二元组 ( u , v ) (u,v)(u,v) 表示,其中 u , v ∈ V u, v \in Vu,v∈V。对于带权图,边由三元组 ( u , v , w ) (u,v, w)(u,v,w) 表示,其中 u , v ∈ V u, v \in Vu,v∈V,w ww 为权值,可以是任意类型。
    9 h6 m9 U/ H4 `图分为有向图和无向图,对于有向图, ( u , v ) (u, v)(u,v) 表示的是 从顶点 u uu 到 顶点 v vv 的边,即 u → v u \to vu→v;对于无向图,( u , v ) (u, v)(u,v) 可以理解成两条边,一条是 从顶点 u uu 到 顶点 v vv 的边,即 u → v u \to vu→v,另一条是从顶点 v vv 到 顶点 u uu 的边,即 v → u v \to uv→u;+ v& W& x9 m4 e7 G  d: x$ A
    2、图的存储
    8 T' ]1 O- C# k/ e对于图的存储,程序实现上也有多种方案,根据不同情况采用不同的方案。接下来以图二-3-1所表示的图为例,讲解四种存储图的方案。* X7 `( g' s6 D5 B. c# D- C

    ) D% m1 ^# k9 ]) Q0 M
    ; U( B% k8 N1 j0 n
    1)邻接矩阵" s3 O* ~) Z& W1 b; L- H
    邻接矩阵是直接利用一个二维数组对边的关系进行存储,矩阵的第 i ii 行第 j jj 列的值 表示 i → j i \to ji→j 这条边的权值;特殊的,如果不存在这条边,用一个特殊标记 ∞ \infty∞ 来表示;如果 i = j i = ji=j,则权值为 0 00。7 S# N, o3 Z" I+ {
    它的优点是:实现非常简单,而且很容易理解;缺点也很明显,如果这个图是一个非常稀疏的图,图中边很少,但是点很多,就会造成非常大的内存浪费,点数过大的时候根本就无法存储。# K5 I3 V. l4 O/ o* ?, |$ Y
    [ 0 ∞ 3 ∞ 1 0 2 ∞ ∞ ∞ 0 3 9 8 ∞ 0 ] \left[
    # ~- W: {1 l0 A& q2 w* n01∞9∞0∞8320∞∞∞309 E+ S" c: O/ q7 L- u& M
    0∞3∞102∞∞∞0398∞0
    . [8 v4 ]7 {1 e\right]
    % ]* r/ n( z, a3 V: {5 r⎣9 S% z% Z: e8 M6 f8 I9 O8 B
    ⎢- `+ B0 Y+ w8 e8 u" L
    ⎢
    ! f* f/ s6 S& f! \3 r: A! P# D⎡
    4 ?" i* K. x6 h​        : K; O2 ^1 E4 l
      
    ' E( m! C- T2 k0 G05 F# ~  s: r" \+ \/ m
    1
    0 \1 X' v' i+ q( q; ~' a∞- X8 _0 k( f5 s! [1 M+ l; R
    95 v% {8 W9 m+ s9 _" |0 R) D0 i
    ​       
    1 }) L0 D5 h6 Q1 N3 T  1 J" p6 }# K) s
    ∞
    / }4 C0 P0 s' }0 }( N8 q0
    0 E/ ^0 Z; U+ e5 x0 T2 q∞& A9 y2 n* p( y/ F. Y4 S
    8
    ; K1 A% ^5 Q5 |2 C0 _8 P​        " B% x  T/ D) H2 ^& G& I
      
    + P7 d* m8 y' Y: _37 ?4 E: y8 p4 J& \5 p8 a+ p
    2
    ; |! }/ {! ~8 ~0
    : w! {$ C2 q  k0 v* H, \∞& `1 H" o4 O$ h# B6 o
    ​        $ D. ^7 O9 t% X4 \1 H
      ! Y2 P( o2 v. \/ a& m, \5 {
    ∞& W: B# R5 R! v( V) j  L/ i6 t
    ∞
    9 N2 E; r& S0 Q; i1 }) }) S& v3; W! Y; `9 f; e( ~  Z8 j2 B
    03 x3 K# j9 j# v& N
    ​        % x/ s: o% _1 L0 m3 x
      
    & h( N  k9 d0 l5 o7 a2 |. }: g⎦5 G% p6 D! m) a% N
    ⎥. N" k1 |* p; A. m) \
    ⎥
    3 x, t1 Y- p6 e& _$ z6 N( ]; j⎤
    ; c- v1 {% L1 F/ Y1 O​       
    6 m9 F# Y, C' h, N9 S  E" ` ( x, b4 R, G  o: f
    2)邻接表
    1 }% V: g1 l/ r" [, k$ E3 S邻接表是图中常用的存储结构之一,采用链表来存储,每个顶点都有一个链表,链表的数据表示和当前顶点直接相邻的顶点的数据( v , w ) (v, w)(v,w),即 顶点 和 边权。
    2 M9 p# U' A* \6 {7 ~" _6 v它的优点是:对于稀疏图不会有数据浪费;缺点就是实现相对邻接矩阵来说较麻烦,需要自己实现链表,动态分配内存。
    ; @8 i/ j! b- ?如图所示,d a t a datadata 即 ( v , w ) (v, w)(v,w) 二元组,代表和对应顶点 u uu 直接相连的顶点数据,w ww 代表 u → v u \to vu→v 的边权,n e x t nextnext 是一个指针,指向下一个 ( v , w ) (v, w)(v,w) 二元组。
    * U' o: P0 {' b# f5 R$ g/ }$ {9 w/ ]) A) b6 R. Z8 o
    2 i2 a8 y- M8 d; `% W0 {
    在 C++ 中,还可以使用 vector 这个容器来代替链表的功能;7 q! N" ^" W  r( C& I5 `5 @- _
        vector<Edge> edges[maxn];. o' [6 ]3 ]) ?+ v& P
    10 a2 \# I( u1 }1 P0 V1 R; K' n
    3)前向星
    . U" T* S. _" @* J前向星是以存储边的方式来存储图,先将边读入并存储在连续的数组中,然后按照边的起点进行排序,这样数组中起点相等的边就能够在数组中进行连续访问了。
    ) a1 [1 q. ~, a2 I2 I0 N, k它的优点是实现简单,容易理解;缺点是需要在所有边都读入完毕的情况下对所有边进行一次排序,带来了时间开销,实用性也较差,只适合离线算法。* x2 |( P1 P& B5 p' Y& ~
    如图所示,表示的是三元组 ( u , v , w ) (u, v, w)(u,v,w) 的数组,i d x idxidx 代表数组下标。
    0 [2 h" Y! Y* O1 E4 x; w0 B9 x, _  Z, e  Z; {

    $ M9 E! k0 U4 H) D1 n6 L1 [; t那么用哪种数据结构才能满足所有图的需求呢?# Q% b+ C& c% v7 |; W
    接下来介绍一种新的数据结构 —— 链式前向星。7 L# W& ~) {% y( E0 [" q! n
    4)链式前向星5 ^! I2 j9 }0 \! ^7 _: H
    链式前向星和邻接表类似,也是链式结构和数组结构的结合,每个结点 i ii 都有一个链表,链表的所有数据是从 i ii 出发的所有边的集合(对比邻接表存的是顶点集合),边的表示为一个四元组 ( u , v , w , n e x t ) (u, v, w, next)(u,v,w,next),其中 ( u , v ) (u, v)(u,v) 代表该条边的有向顶点对 u → v u \to vu→v,w ww 代表边上的权值,n e x t nextnext 指向下一条边。! Y) ]8 Z* d. u+ f4 q. _
    具体的,我们需要一个边的结构体数组 edge[maxm],maxm表示边的总数,所有边都存储在这个结构体数组中,并且用head来指向 i ii 结点的第一条边。
    1 d: p: v8 [4 E: R边的结构体声明如下:
    # z. F& N8 {' b2 x3 f+ ^% J  ~struct Edge {
    2 R" d; @3 U: A# Q    int u, v, w, next;4 m- _( A) P  j$ P
        Edge() {}" e! ^# j& w8 `, Z5 e9 f( t
        Edge(int _u, int _v, int _w, int _next) :, r) T# `: b' i* r+ X5 f
            u(_u), v(_v), w(_w), next(_next) 8 v( N, n' F  x) B7 `/ h2 ]: ]/ e
        {* L  {3 X. m  k0 w2 F* H8 o! ~
        }
    5 G3 _1 `$ ~) T* T* I}edge[maxm];+ g9 k7 ^( N, J1 v" _  R$ M
    1
    & Y2 c( C* m: d+ @6 o2! r8 y2 K; F4 X) z
    3
    8 w- i( c! ~3 s7 F7 G2 {: ~# t4
    - I+ a( Q3 h  h  K: V. c5
    " O0 p1 r* M9 I; d# @, Z' I+ e9 l6
    0 T% Z3 r" w" l% t7 V7 T8 n( x8 J78 d3 C! T* K1 Z- ]/ p4 W
    8
    # i: m0 e& |. D) ?- h! I7 J- z初始化所有的head = -1,当前边总数 edgeCount = 0;
    + v0 d! K7 P, n$ R每读入一条 u → v u \to vu→v 的边,调用 addEdge(u, v, w),具体函数的实现如下:
    5 q* n$ b$ H* D8 s# r: Ovoid addEdge(int u, int v, int w) {
    ( @+ o7 Z9 m- c8 g3 Z    edge[edgeCount] = Edge(u, v, w, head);/ |2 h$ f6 X2 U" @4 I* L
        head = edgeCount++;/ v1 o4 T: m, Q8 w7 C! _% g" D
    }
    # ]$ o8 V0 w0 C& C- n; Z1
    % ]$ l5 Y/ b, r- U2
    - G; K! S% H& }% e0 W35 l/ w+ t  F  i) ]; t
    4$ m5 {( I' G, O8 k; a! B
    这个函数的含义是每加入一条边 ( u , v , w ) (u, v, w)(u,v,w),就在原有的链表结构的首部插入这条边,使得每次插入的时间复杂度为 O ( 1 ) O(1)O(1),所以链表的边的顺序和读入顺序正好是逆序的。这种结构在无论是稠密的还是稀疏的图上都有非常好的表现,空间上没有浪费,时间上也是最小开销。
    # p: q4 ^5 T6 H8 H: o! t调用的时候只要通过head就能访问到由 i ii 出发的第一条边的编号,通过编号到edge数组进行索引可以得到边的具体信息,然后根据这条边的next域可以得到第二条边的编号,以此类推,直到 next域为 -1 为止。% \( A" w0 B! m2 B* v; O; C
    for (int e = head; ~e; e = edges[e].next) {0 p( n2 ]# x- |4 ~: V* m3 b* m
        int v = edges[e].v;: i" }5 A2 e6 o5 s
        ValueType w = edges[e].w;
    1 a- K4 t  j; p& @5 J! s    ...! N  r4 g  r! |6 z, z2 Z7 m
    }4 ^, _* q/ ]# s  g; j
    1
    1 W* f/ B: k6 q7 q) U7 k2. }/ U: K; _4 C9 h
    3) ~; X1 O6 h& H! _2 J- X: L5 G
    43 R- Q8 A, h2 c! ?6 U" \+ q
    5( ^* B( T0 R- S3 N3 l
    文中的 ~e等价于 e != -1,是对e进行二进制取反的操作(-1 的的补码二进制全是 1,取反后变成全 0,这样就使得条件不满足跳出循环)。3 m2 E; I5 v2 {3 ^( p5 v* E
    4、算法入门* H: U- o1 y1 N# x3 l; N
    算法入门,其实就是要开始我们的刷题之旅了。先给出思维导图,然后一一介绍入门十大算法。3 d  `5 J& }. B) {5 H1 A

    ( {5 u9 X% k- @& a  }- {& R  f

    + {" S7 E* J) ~入门十大算法是 枚举、排序、模拟、二分、双指针、差分法、位运算、贪心、迭代、分治。
    ; |" \+ I: ^! I6 Y& H+ v对于这十大算法,我会逐步更新道这个专栏里面:《LeetCode算法全集》。9 \' Z. I9 \; X1 z3 e
    1、枚举
    0 a; [% G. ]! s8 `; m8 J枚举可以简单理解成for循环,从一个数组中遍历查找一个值,就是枚举;从一个数组中找到一个最大值,就是枚举;求数组所有数的和,也是枚举。2 F2 P, t0 r  C& A
    对于枚举而言,基本就是循环语句的语法学会,这个算法就算学会了。/ |% k3 I6 D, l* A4 s$ }
    2、排序$ k- ]% Z4 q8 F4 v! n
    既然是入门,千万不要去看快排、希尔排序这种冷门排序。
    9 u( i9 [4 Q" C1 j冒泡排序、选择排序、简单插入排序 原理好懂,先看懂再说,其他不管。因为这三者都是基于枚举的。
      M5 b6 ]( [0 o6 kC中有现成qsort排序函数,C++中有现成 sort排序函数,直接拿来用,等算法进阶时再回头来看快速排序的算法实现。; W! I  ?; o, u+ L0 W
    3、模拟
    ! o8 ?( q. n- w- E3 `' @# u3 d8 [模拟就是要求做什么,你就做什么,完全不要去考虑效率问题。
    1 |! X4 {" H9 H$ L# f' o# Q不管时间复杂度 和 空间复杂度,放手去做!1 i* s1 [$ `# y, r9 ]  k7 _
    但是,有时候模拟题需要一些复杂的数据结构,所以模拟题难起来也可以很男,难上加难。
    4 J1 p7 u7 y# |% _$ I4 e' t8 x% E  \4、二分; c9 M. x- W5 b1 a/ m  v
    二分一般指二分查找,当然有时候也指代二分枚举。
    " \* _; y# i) ~* f8 w9 ]+ C3 t2 h例如,在一个有序数组中查找值,我们一般这个干:' f% y1 s  ]0 \7 ?4 x* h; e* |$ r
    1)令初始情况下,数组下标从 0 开始,且数组长度为 n nn,则定义一个区间,它的左端点是 l = 0 l=0l=0,右端点是 r = n − 1 r = n-1r=n−1;$ A" K1 v2 {3 x2 g  \4 ]2 i
    2)生成一个区间中点 m i d = ( l + r ) / 2 mid = (l + r) / 2mid=(l+r)/2,并且判断 m i d midmid 对应的数组元素和给定的目标值的大小关系,主要有三种:+ X$ U3 x1 S- S$ y3 l, N. e/ J
      2.a)目标值 等于 数组元素,直接返回 m i d midmid;4 U, q# |- W& W' s! s5 m4 r
      2.b)目标值 大于 数组元素,则代表目标值应该出现在区间 [ m i d + 1 , r ] [mid+1, r][mid+1,r],迭代左区间端点:l = m i d + 1 l = mid + 1l=mid+1;/ v; {0 p; T$ {2 ?/ n1 {
      2.c)目标值 小于 数组元素,则代表目标值应该出现在区间 [ l , m i d − 1 ] [l, mid-1][l,mid−1],迭代右区间端点:r = m i d − 1 r = mid - 1r=mid−1;
    $ ^1 L* w  K$ J' A3)如果这时候 l > r l > rl>r,则说明没有找到目标值,返回 − 1 -1−1;否则,回到 2)继续迭代。! A; W, n! o1 i# b
    5、双指针
    ' f  S  t/ y: A; E9 B双指针,主要是利用两个下标在一个数组上,根据问题的单调性,进行指针偏移,由于每个指针只往后偏移,所以时间复杂度可以达到 O ( n ) O(n)O(n),由于思想非常简单,所以出题时,热度不低。8 w0 g4 I) K& J8 X% L, ~& X4 s* ]( X- h& C
    * S* L3 ?; H/ r* I4 {- a

    ; d! \! b+ C! o' g  t6、差分法" ?& @$ C( w  n1 l! G
    差分法一般配合前缀和。  {7 P& r( {8 z7 j
    对于区间 [ l , r ] [l, r][l,r] 内求满足数量的数,可以利用差分法分解问题;! `7 r( }  b" a. p' C! A
    假设 [ 0 , x ] [0, x][0,x] 内的 g o o d   n u m b e r good \ numbergood number 数量为 g x g_xg
    . r! U# ?6 x; r. a1 H- }4 X- `x& E9 C4 N# h' B# f3 O" _: s* p  ~
    ​       
    ; p: s; v  z. R6 d ,那么区间 [ l , r ] [l, r][l,r] 内的数量就是 g r − g l − 1 g_r - g_{l-1}g , n# v9 r2 G! |$ F* c) h; T* O
    r4 I5 j2 Z# s4 E$ p' \2 s, W; A
    ​       
    : v" X$ X/ i4 K8 _/ N% n9 D −g - Y. q, _) M5 {* f' ], F  `
    l−1
    7 s; r# H  f/ O2 z​       
    # L9 G7 \- G" J2 A9 U ;分别用同样的方法求出 g r g_rg " k+ H" M8 ^9 L6 O/ g( q7 n( p1 j' J
    r' _' G# Q9 L7 |9 d+ l% p3 m3 n
    ​       
    * p9 ^+ k/ o) u% [" a  和 g l − 1 g_{l-1}g
    + F% N; p5 e8 a; Ll−1/ {' j- F9 i3 s: D% H
    ​       
    5 J' N# n. V( d$ h+ r* Y5 M: L ,再相减即可;
    6 Q7 p/ q1 h3 T: H
    ( t5 H9 f8 O1 ~5 i2 V# Q
    7 ?3 t" E9 R, A& a$ ]7 ^6 q  Y# E
    7、位运算
    + g" A5 M% \. W" o: g8 |: D位运算可以理解成对二进制数字上的每一个位进行操作的运算。9 p/ @2 x( U7 K& i7 u& t; ?+ @
    位运算分为 布尔位运算符 和 移位位运算符。2 F  @* p4 A9 o9 Z& i; d) `: g$ ]; \
    布尔位运算符又分为 位与(&)、位或(|)、异或(^)、按位取反(~);移位位运算符分为 左移(<<) 和 右移(>>)。/ w: k0 y. p5 z. p2 a
    如图所示:
    ; m: d# E# D+ s+ c/ ?5 m  A* [& u6 A& `  {8 V

    ( x$ Q. z4 \6 o, e位运算的特点是语句短,但是可以干大事!
    & b+ \+ q5 d/ z, I& Q比如,请用一句话来判断一个数是否是2的幂,代码如下:1 o* N; x2 o2 Q. U" ?5 X
    !(x & (x - 1))0 J  Q9 a1 n& ]6 R1 b6 x
    13 Q* {/ L% P+ N9 L5 O0 R( L6 h
    8、贪心
    7 A5 W+ L0 j( h! r8 m" b1 L, p+ {贪心,一般就是按照当前最优解,去推算全局最优解。4 w+ [) a" \# P0 @4 q$ T* h
    所以,只有当当前最优解和全局最优解一致时才能用贪心算法。贪心算法的证明是比较难的,但是一些简单的贪心问题会比较直观,很容易看出来这个能够这么贪。! c' d+ G! g# A- G6 V* Q
    9、迭代, F- E; `1 l: r* e- P- V5 v
    每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值,周而复始,直到问题全部解决。" b& o* e. D" I( ^$ h7 U. U! I
    10、分治
    % z1 _0 c9 U/ n( F. U分治,就是把问题分成若干子问题求解,子问题解决后,问题就解决了。一般利用递归实现。属于初学者比较头疼的内容。递归一开始学习的时候,一定要注意全局变量和局部变量的关系。) n0 Q3 O8 j5 f, H# \8 P& q: }
    5、算法进阶
    ( N, }+ G7 z; l* |$ [2 {# P5 j算法进阶这块是我打算规划自己未来十年去完成的一个项目,囊括了 大学生ACM程序设计竞赛、高中生的OI竞赛、LeetCode 职场面试算法 的算法全集,也就是之前网络上比较有名的 《夜深人静写算法》 系列,这可以说是我自己对自己的一个要求和目标吧。( g) t  T0 Q8 y6 F6 N& P
    如果只是想进大厂,那么 算法入门 已经足够了,不需要再来看算法进阶了,当然如果对算法有浓厚兴趣,也欢迎和我一起打卡。由于内容较难,工作也比较忙,所以学的也比较慢,一周基本也只能更新一篇。, b$ p5 i; o! }1 Y5 L
    这个系列主要分为以下几个大块内容:: h+ U  r/ H% ^; \
      1)图论
    & s, C; i7 c( X& |# A  X- y  2)动态规划
    & m$ p) W% c1 Y9 T9 R& w. V& p. i  3)计算几何
    ' D6 M6 x3 P* z/ e  4)数论9 E) Q4 ]8 i* ]2 q4 {. N
      5)字符串匹配, i. R: S6 T+ R$ q1 |# r
      6)高级数据结构(课本上学不到的)0 ?1 m# M" f7 X, ^/ ^8 W# z
      7)杂项算法
    9 i: k2 B; \7 E9 q  r4 C, r2 q& v+ H: W& P- |

      u# X; U" n) n6 c/ h先来看下思维导图,然后我大致讲一下每一类算法各自的特点,以及学习方式:- y( D" D; F2 _  ^2 P1 b& }" q
    7 E$ ]5 d: p6 H3 t/ Z% b2 a
    0 C* n1 `% T( a$ D' F4 c" r( S  g! y

    7 t, ~0 P( q, W0 S/ K; x4 y

    : d! H( |8 n7 C: G6 J1)图论
    3 T; ~: _% y- T& X7 H1、搜索概览1 t- t  i" m) W
    图论主要围绕搜索算法进行展开。搜索算法的原理就是枚举。利用计算机的高性能,给出人类制定好的规则,枚举出所有可行的情况,找到可行解或者最优解。
    * J7 z1 E9 N6 j1 s* }) B2 v- n* J# b1 s% ?
    ! e& b7 Z' o9 d) Z
    比较常见的搜索算法是 深度优先搜索(又叫深度优先遍历) 和 广度优先搜索(又叫广度优先遍历 或者 宽度优先遍历)。各种图论的算法基本都是依靠这两者进行展开的。* a. o0 k6 Z3 [& z" T, T& T
    2、深度优先搜索7 g3 A1 c  u. e1 ^* d; a
    深度优先搜索一般用来求可行解,利用剪枝进行优化,在树形结构的图上用处较多;而广度优先搜索一般用来求最优解,配合哈希表进行状态空间的标记,从而避免重复状态的计算;8 o. a9 Z5 ?) f" Y% D+ p; ?
    原则上,天下万物皆可搜,只是时间已惘然。搜索会有大量的重复状态出现,这里的状态和动态规划的状态是同一个概念,所以有时候很难分清到底是用搜索还是动态规划。
      j8 K/ ]) I4 m. W7 u3 u0 ~但是,大体上还是有迹可循的,如果这个状态不能映射到数组被缓存下来,那么大概率就是需要用搜索来求解的。, B, M! C7 S; b, Y& w) r, p, c, g
    如图所示,代表的是一个深度优先搜索的例子,红色实箭头表示搜索路径,蓝色虚箭头表示回溯路径。2 ^% G2 Z- j: a# n. y
    8 m  }+ Z) x6 P; o7 i+ |
    ! D9 R* v; y4 U' }6 M0 D
    红色块表示往下搜索,蓝色块表示往上回溯,遍历序列为:# y: E" v5 G% w1 t: \
            0 -> 1 -> 3 -> 4 -> 5 -> 2 -> 6
    3 A4 R+ _3 g0 E4 |. E2 u) D1
    $ Z% e- X3 k& k  o+ @3 i* g1 L) L5 e同样,搜索的例子还有:4 i7 R0 _3 w: Q( O8 n/ j" ~* B3 R3 a
    9 D+ v" J$ M& ?
    , j  A  j4 ?9 W( g3 e& ^5 n/ h
    计算的是利用递归实现的 n nn 的阶乘。
    5 K/ A) I9 m: w/ e. N  c" p3、记忆化搜索9 W4 G( r  p* O4 C+ b% ?$ R9 M
    对于斐波那契函数的求解,如下所示:
    ) \# j0 }2 L  |! x1 k4 {; a' A5 nf ( n ) = { 1 ( n = 0 ) 1 ( n = 1 ) f ( n − 1 ) + f ( n − 2 ) ( n > 2 ) f(n) =
    5 k9 G5 m( C2 \7 D. Q5 D⎧⎩⎨11f(n−1)+f(n−2)(n=0)(n=1)(n>2)/ z' a* R% Z  N$ s6 f' a0 l
    {1(n=0)1(n=1)f(n−1)+f(n−2)(n>2)6 D3 C3 q0 n8 Y: c
    f(n)= & z( Y' [% |" S! I: j) C
    ⎩
    : L: d: M: ?5 T# E⎪
    2 L  _7 _, D, u$ k⎨
    $ a6 J) k2 n9 D⎪5 P$ e+ t4 W' b/ Q8 U- w
    ⎧; v9 O) `5 }9 L. Q  m
    ​       
    2 Z, L8 Q; u+ r3 i  
    $ m& H$ b4 l- B! K1
    ) M+ e( s3 r- d; C1" `" j3 H) F* K# T
    f(n−1)+f(n−2)! U6 ]2 V6 k- U; m; |
    ​        9 C' C" B$ |% l* u* L( d. B
      
    1 g/ p3 v4 d1 [* n9 i+ e7 J9 i(n=0)
    # Q  q5 c6 _9 V6 H. T$ n(n=1)
    : _+ e: I% E% i2 X4 Y: U(n>2)1 t4 s7 U, I: x' {5 L* j. t3 D1 k  C
    ​        % _+ z" `& w; [6 E1 V6 v* t9 @

    ! I6 V- x" H6 r对于 f ( 5 ) f(5)f(5) 的求解,程序调用如下:) c- \/ P! u+ B" O% M

    " }; y+ J9 {2 i( ^5 B( r

    8 [* E& X; T# r# d2 w" ^; ]这个过程用到了很多重复状态的搜索,我们需要将它优化,一般将一些状态缓存起来。) @: G* U' @  H, u! C4 Y
    我们通过一个动图来感受一下:
    % i# C# @4 u1 _- H
    . p( Z: w6 X8 s6 P8 z9 e" ?
    * M7 e; M. D+ [+ t! z) R& g: [8 v
    当第二次需要计算 f ( 2 ) f(2)f(2) 和 f ( 3 ) f(3)f(3) 时,由于结果已经计算出来并且存储在 h [ 2 ] h[2]h[2] 和 h [ 3 ] h[3]h[3] 中,所以上面这段代码的fib != inf表达式为真,直接返回,不再需要往下递归计算,这样就把原本的 “递归二叉树” 转换成了 “递归链”, 从而将原本指数级的算法变成了多项式级别。
    / i- r( d; W: D! ~$ }9 E  {这就是记忆化搜索,像这种把状态缓存起来的方法,就是动态规划的思想了。
      q) Q% l- K* o' v: ?5 }0 h$ B$ G4、广度优先搜索: u  V  e& K/ w1 f
    单向广搜就是最简化情况下的广度优先搜索(Breadth First Search),以下简称为广搜。游戏开发过程中用到的比较广泛的 A* 寻路,就是广搜的加强版。
    " S1 c, j! t- f' F$ l6 i0 p4 B我们通过一个动图来对广搜有一个初步的印象。
    3 ~% P& K+ s3 I7 o6 ~' f7 d- m/ T/ P8 y& |5 x

    4 v' K# a$ G  c2 N5 y2 D* E& J& l- U' G- p

    4 r- S$ S' O* P+ n9 H从图中可以看出,广搜的本质还是暴力枚举。即对于每个当前位置,枚举四个相邻可以行走的方向进行不断尝试,直到找到目的地。有点像洪水爆发,从一个源头开始逐渐蔓延开来,直到所有可达的区域都被洪水灌溉,所以我们也把这种算法称为 FloodFill。3 u! U0 K$ U: p2 v
    那么,如何把它描述成程序的语言呢?这里需要用到一种数据结构 —— 队列。, E0 H$ O$ H" |0 Z+ i
    这时候,算法和数据结构就完美结合了。
    $ ^) v& R3 @+ F: D2)动态规划
    8 C# r, D( Y; d4 T) {动态规划算法三要素:) p2 G0 k3 k% \  {" i. y' t
      ①所有不同的子问题组成的表;1 a/ Q% z% [/ r3 V# ]9 f; U
      ②解决问题的依赖关系可以看成是一个图;3 e+ W# W# r9 ^  ]* |! |" ~/ I
      ③填充子问题的顺序(即对②的图进行拓扑排序,填充的过程称为状态转移);
    ' o( G# e# c8 k. [& V$ x4 z, p. T1 M2 R! ]) s. s
    , x. I4 h" `" c* c8 O7 X
    如果子问题的数目为 O ( n t ) O(n^t)O(n # M: M; Q. Q  G0 y
    t8 I; j# t7 {- W  r( @6 B
    ),每个子问题需要用到 O ( n e ) O(n^e)O(n - F+ F" @4 S  v; F. e  I- L& j
    e
    # ^8 a5 k0 F4 {% V$ ~2 \1 @ ) 个子问题的结果,那么我们称它为 tD/eD 的问题,于是可以总结出四类常用的动态规划方程:(下面会把opt作为取最优值的函数(一般取 m i n minmin 或 m a x maxmax ), w ( j , i ) w(j, i)w(j,i)为一个实函数,其它变量都可以在常数时间计算出来)。: ]& b' h- p7 q" A
    1、1D/1D
    7 P6 f, A2 y% L. Xd [ i ] = o p t ( d [ j ] + w ( j , i ) ∣ 0 < = i < j ) d = opt( d[j] + w(j, i) | 0 <= i < j )
    " f9 Q- c7 h9 z- z0 bd=opt(d[j]+w(j,i)∣0<=i<j)
    . ^- k: i$ q6 s6 [状态转移如图四所示(黄色块代表d [ i ] dd,绿色块代表d [ j ] d[j]d[j]):
    , Q) m, r* S/ e( U; r( J% {/ f$ l% D! ^* O/ r4 j3 g( J

    : P' `4 ?: ], g1 B9 z这类状态转移方程一般出现在线性模型中。/ |3 W$ t* o" n3 M5 S: W
    2、2D/0D
    ( ^0 z- A% B7 id [ i ] [ j ] = o p t ( d [ i − 1 ] [ j ] + x i , d [ i ] [ j − 1 ] + y j , d [ i − 1 ] [ j − 1 ] + z i j ) d[j] = opt( d[i-1][j] + x_i, d[j-1] + y_j, d[i-1][j-1] + z_{ij} )' z& h6 {( n9 q) U$ k3 M
    d[j]=opt(d[i−1][j]+x 8 H1 [, |7 `* D2 ^7 a
    i
    & g8 F8 C  b( W3 a* b3 C/ J7 Q4 z6 K$ s​        * S5 m' x3 U, N( x) O0 O- \
    ,d[j−1]+y
    8 A- |6 @9 K$ F, L* ?j
    8 @, @1 L* Q4 w) B8 x' E0 y! E​        - e" f5 T. n, |& B1 R) i7 t% A* h
    ,d[i−1][j−1]+z
    - c9 d. c& T( V0 R2 b$ z. A9 Aij% X9 ]/ _& l$ i9 [2 v% I6 A1 r- l
    ​       
    8 G0 f7 l" q4 }8 _5 b )/ o. O6 O& k0 a/ L1 U# h* C
    状态转移如图四所示:
    0 w# {) Z, ?6 v) {  E/ L0 A% N6 U6 i% g9 p

    ! @+ F/ E6 |# k: ~. F9 U: ^比较经典的问题是最长公共子序列、最小编辑距离。; P. H5 E0 H# g* K7 `1 @
    有关最长公共子序列的问题,可以参考以下文章:夜深人静写算法(二十一)- 最长公共子序列
    ' V/ c# b$ |( O, B( b$ [* z' I有关最小编辑距离的问题,可以参考以下文章:夜深人静写算法(二十二)- 最小编辑距离
    . Y, l; @, _1 U' s6 I3、2D/1D
    & H! f5 Z* N4 L3 s! kd [ i ] [ j ] = w ( i , j ) + o p t ( d [ i ] [ k − 1 ] + d [ k ] [ j ] ) d[j] = w(i, j) + opt( d[k-1] + d[k][j] )
    + X* D- y+ q" i: V6 zd[j]=w(i,j)+opt(d[k−1]+d[k][j])
    $ G& d/ B) z5 B. T1 [区间模型常用方程,如图所示:
    0 Q( k" Y: c: G
    : I7 \; h, K5 _- h' R% j% ?
    * |( b- @3 A% L! [' `
    另外一种常用的 2D/1D 的方程为:
    / ^* L- j! y+ f# c3 id [ i ] [ j ] = o p t ( d [ i − 1 ] [ k ] + w ( i , j , k ) ∣ k < j ) d[j] = opt( d[i-1][k] + w(i, j, k) | k < j )
    ! t6 Q) B9 D0 ^" K: Md[j]=opt(d[i−1][k]+w(i,j,k)∣k<j)
    $ o# I/ d& o" X3 P( i区间模型的详细内容可以参考以下这篇文章:夜深人静写算法(二十七)- 区间DP$ s: j! I: j9 t) H. i: R# |
    4、2D/2D
    5 k# ^: l) @0 R9 ~  L7 \- V0 n' r$ Fd [ i ] [ j ] = o p t ( d [ i ′ ] [ j ′ ] + w ( i ′ , j ′ , i , j ) ∣ 0 < = i ′ < i , 0 < = j ′ < j ) d[j] = opt( d[i'][j'] + w(i', j', i, j) | 0 <= i' < i, 0 <= j' < j)% o* l1 j! R" [# h, r+ L
    d[j]=opt(d[i
      d! N4 F8 R0 g/ v, g  E! N′
    + ?1 D8 h2 i- m; x+ u ][j 4 D! Z* e7 R* d. B3 g7 K- L9 K
    ′- s/ d$ l2 b; }
    ]+w(i
    ! y2 N5 e& y5 c3 N3 m: U6 ~′
    ; T5 u9 I0 q8 ]; R, W, o* j7 @, F ,j   y* y' S% z8 ^% F
    ′) M5 J; u- \; Q% g) I' U+ I# o
    ,i,j)∣0<=i
    5 M, q! Y1 i: K* `% M! z/ z' y′
    & t' H1 Q; a3 l <i,0<=j
    6 f; D2 L" |* K$ l5 U- v′! }% U, ?- J" v) A' r
    <j)4 o+ ^. \) L) ^9 P+ G7 h2 t5 `
    如图所示:7 l) i6 d: H2 V7 k+ O* c

    4 {- z' d% q. m5 K0 c6 `

    $ h' o3 V) M" m( ?常见于二维的迷宫问题,由于复杂度比较大,所以一般配合数据结构优化,如线段树、树状数组等。
    6 V: r, I1 D/ |; o# |) h7 k  l1 ]对于一个tD/eD 的动态规划问题,在不经过任何优化的情况下,可以粗略得到一个时间复杂度是O ( n t + e ) O(n^ {t+e})O(n
    9 O* q; U8 g  z+ f' G1 ht+e
    % O  v& L" k1 e% m ),空间复杂度是O ( n t ) O(n^t)O(n
    # U$ G1 |8 |( ~' g7 ^5 s% I  |, mt9 G8 M; J7 W# l* E' q
    ) 的算法,大多数情况下空间复杂度是很容易优化的,难点在于时间复杂度,后续章节将详细讲解各种情况下的动态规划优化算法。$ q* B( o8 ^0 X' B8 u" n1 S) L
    3)计算几何
    ( G9 e8 x. m1 W" U' o计算几何的问题是代码量最大的。它是计算机科学的一个分支,以往的解析几何,是用代数的方法,建立坐标系去解决问题,但是很多时候需要付出一些代价,比如精度误差,而计算几何更多的是从几何角度,用向量的方法来尽量减少精度误差,例如:将除法转化为乘法、避免三角函数等近似运算 等等。4 i7 n0 }# J0 {* N5 \
    如果一个比赛中,有一道计算几何的题,那么至少,它不会是一道水题。: S/ i" b: u* w6 S: H  d
    1、double 代替 float
    0 h  v9 ]5 O2 ~/ lc++ 中 double 的精度高于 float,对精度要求较高的问题,务必采用 double;; z# J/ L8 J% t; ]
    2、浮点数判定
    % o8 A. w3 `4 E" \# q! N$ r, [由于浮点数(小数)中是有无理数的,即无限不循环小数,也就是小数点后的位数是无限的,在计算机存储的时候不可能全部存下来,一定是近似的存储的,所以浮点数一定是存在精度误差的(实际上,就算是有理数,也是存在误差的,这和计算机存储机制有关,这里不再展开,有兴趣可以参见我博客的文章:C++ 浮点数精度判定);
    9 x- L  V) h5 L; k  P7 m( V两个浮点数是否相等,可以采用两数相减的绝对值小于某个精度来实现:7 h( L1 f+ U) X  ?
    const double eps = 1e-8;5 T: i/ R2 m8 Z  C, G; C3 l! a
    bool EQ(double a, double b) {) \8 n5 r: O! G4 A# f8 K$ m6 H
        return fabs(a - b) < eps;7 w& e( {1 x; M
    }2 |8 l4 V: ^" C
    1
      F1 F1 y1 n% r- |0 `2
    3 n# J2 l5 m1 S+ F& i% _0 v3
    : n' ]6 U" J7 |6 U( m2 p. C45 J! C# r0 Z+ c
    并且可以用一个三值函数来确定某个数是零、大于零还是小于零:( }, Q$ ^8 W" s9 Q. ]+ Q. W
    int threeValue(double d) {
    # @3 s' ^0 h) n: S' }0 Z9 x    if (fabs(d) < eps)
    ( t/ a/ C9 u* K8 K& Z        return 0;) X, K  ]$ N' O+ a( z
        return d > 0 ? 1 : -1;, X, J2 p# P1 O- j' A$ _2 f
    }- ?0 x! ~! j4 J5 o. }
    11 |. ]: s- a$ f( B
    2
    5 `! y% r' i, K1 y( ~4 M' ?1 W1 n% ^3& B' B; T) |  K  e
    42 s$ @0 V  f8 @# F; a
    5
      ?  e# i. j: T6 X6 o3、负零判定: i7 k, P! n$ K( a+ J% {% U- q
    因为精度误差的存在,所以在输出的时候一定要注意,避免输出 -0.00:
    9 M% w, D, |4 o7 \) t! g; u7 X    double v = -0.0000000001;
    - S3 G" w6 }7 g* ^    printf("%.2lf\n", v);- O9 X) i; F" a$ y, j
    1
    1 E4 d5 F& k" o6 E2
    + j9 ]" g/ w* b( C1 G- G避免方法是先通过三值函数确定实际值是否为0,如果是0,则需要取完绝对值后再输出:
    * k! j& y: b, }, T    double v = -0.0000000001;
    : u8 ]  p4 g9 v+ ~/ J    if(threeValue(v) == 0) {
    & k7 ~' w" I  i* h/ |" r        v = fabs(v);
    " s% B  D) s8 f, A    }8 }, R3 T2 Y. H) E  {9 @3 ?
        printf("%.2lf\n", v);2 D4 a2 F" J9 z, ]8 m9 G5 B$ i8 z2 N" J
    1
    7 }$ B( |7 V4 W0 m21 Y* y$ S# X# I5 j/ E
    3- q; c  Q" Q# ?' u
    4
    ' [' ]1 _( L9 a) |5
    0 I0 f' W! c, x" n: M: ~: g4 O4、避免三角函数、对数、开方、除法等
    - P+ L0 a% c' y$ [c++ 三角函数运算方法采用的是 CORDIC算法,一种利用迭代的方式进行求解的算法,其中还用到了开方运算,所以实际的算力消耗还是很大的,在实际求解问题的过程中,能够避免不用就尽量不用。0 Y6 g' @+ v, W3 \5 I- m$ a! B
    除法运算会带来精度误差,所以能够转换成乘法的也尽量转换为乘法运算。7 x! p/ q* V) E& j& |7 d
    5、系统性的学习  \. k8 m4 B6 a# [& t4 F8 h/ w
    基础知识:点、向量、叉乘、点乘、旋转、线段、线段判交、三角形面积;
    ' l! `( k4 B( s0 H, }, Y进阶知识:多边形面积、凸多边形判定、点在多边形内判定;# C# c2 b( U7 Q( i
    相关算法:二维凸包、三维凸包、旋转卡壳、多边形面积交、多边形面积并、多边形面积异或、多边形和圆的面积交、半平面交、最小覆盖圆、最小包围球、模拟退火。5 d* c+ p/ ^! A3 B2 u

    - R  d1 \7 X) h

    ; v( {# [! E/ x( Q9 }* r9 u7 ]学习计算几何,最好是系统性的,刷题的过程中不断提炼出自己的模板。1 |. V' f9 e: x* {8 Y% v
    4)数论0 e( z4 V# r7 ?# d1 X3 q! H
    刷题的时候遇到不会的数论题,真的是很揪心,从头学起吧,内容实在是太多了,每个知识点都要证明吃透,不然下次遇到还是不会;不学吧,又不甘心,就是单纯的想把这个题过了,真是进退两难!
    ' p$ N. I, u% b% U7 D6 u数论对一个人的数学思维要求较高,但是一般也是一些固定的模式,所以把模板整理出来很重要。: [* D' d' D! m! O* Q4 u6 A
    当然,数论也有简单问题,一般先做一些入门题提升信心。& _- f  D% j6 t, M4 a8 N
    1、数论入门
    , P. {/ P: `$ s) y/ R; o9 p  B/ |主要是一些基本概念,诸如:* j$ l; ^  f$ W
    整除性、素数与合数、素数判定、素数筛选法、因数分解、算术基本定理、因子个数、因子和、最大公约数 (GCD) 和 最小公倍数 (LCM)、辗转相除、同余、模运算、快速幂取模、循环节;9 x' N- I) F8 d: s0 {% @$ I
    2、数论四大定理
    1 s. S) q; d/ H+ r( v4 e这四个定理学完,可以KO很多题:
    9 K5 ?! Y& z# M: g6 q欧拉定理、中国剩余定理、费马小定理、威尔逊定理
    % J+ H  h( h7 p% d- K. s3、数论进阶* l8 C; e$ A0 [) ^0 i
    系统性的学习,基本也就这些内容了:- i& `- z; Q3 y' x# R  s
    扩展欧几里得、逆元、欧拉函数、同余方程组、扩展欧拉定理、RSA、卢卡斯定理、整数分块、狄利克雷卷积、莫比乌斯反演、大数判素、大数因子分解、大步小步离散对数等等。0 I9 C3 {% R" L9 P; q5 Q- c. s
    5)字符串匹配) s5 q8 N$ ?% H& Z, V: ^7 J* C
    字符串匹配学习路线比较明确。
    3 `0 a8 o: c1 |# B7 b8 m; E3 h先学习前缀匹配:字典树。
    , G$ `& I( c3 f然后可以简单看一下回文串判定算法:Manacher。
    2 l6 k8 f2 E: q" W以及经典的单字符串匹配算法:KMP。
    9 n6 ^; d+ d$ }- o# T实际上平时最常用的还是 BM 算法,而ACM中基本不考察。$ G* _2 o8 W  ]" S& Z: B. T7 }
    然后就是较为高阶的 前缀自动机、后缀数组、后缀树、后缀自动机了。
    7 a5 n/ U& {; j1 f+ }# k关于 算法学习路线 的内容到这里就结束了。
    & T# t: `- c3 @+ b; {5 Z$ ~. ^如果还有不懂的问题,可以 想方设法 找到作者的微信进行在线咨询。- G1 h0 `4 ]8 C; x
    参考资料
    % ^! P# T) o; S8 t【阶段一】C语言学习资料:《光天化日学C语言》(日更)
    , O& g8 x- c& T: O【阶段二】C语言例题:《C语言入门100例》(日更); [! r9 n) x7 e- _0 Q) I: e
    【阶段三】算法入门题集:《LeetCode算法全集》(日更)" s' K+ d' m  D5 w/ H. K
    【阶段四】算法进阶:《夜深人静写算法》(周更)8 L% s. `3 _8 _0 c0 V0 G& h6 _" O4 A
    ————————————————
    ' ~! {" @. E- R3 B3 y版权声明:本文为CSDN博主「英雄哪里出来」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    6 r; b4 C& o* w原文链接:https://blog.csdn.net/WhereIsHeroFrom/article/details/118382228- D. c3 I# {- @3 w, u
    - m: P3 g% r2 w
    7 ?5 Y7 K+ }  M  i& r& K# F
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信

    0

    主题

    10

    听众

    299

    积分

    升级  99.5%

  • TA的每日心情
    开心
    2023-10-14 10:28
  • 签到天数: 28 天

    [LV.4]偶尔看看III

    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

    关于我们| 联系我们| 诚征英才| 对外合作| 产品服务| QQ

    手机版|Archiver| |繁體中文 手机客户端  

    蒙公网安备 15010502000194号

    Powered by Discuz! X2.5   © 2001-2013 数学建模网-数学中国 ( 蒙ICP备14002410号-3 蒙BBS备-0002号 )     论坛法律顾问:王兆丰

    GMT+8, 2026-9-27 17:48 , Processed in 0.314310 second(s), 56 queries .

    回顶部