QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 4481|回复: 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
    * F5 v: ]$ m: m5 x: D
    ❤️两万字《算法 + 数据结构》全套路线❤️(建议收藏)
    0 m/ D3 Y7 F' B) y8 ~. J  l/ y
    : w6 y6 T0 F6 f2 \, \. Q, Z前言
    ) s7 ^4 u+ `0 j8 p, t# V. Q  所谓活到老,学到老,虽然我感觉自己已经学了很多算法了,但是昨天熬夜整理完以后发现,自己还是个弟弟,实在忍不住了,打算把 算法学习路线 发出来,我把整个算法学习的阶段总结成了五个步骤,分别为: 基础语法学习(重要)、语法配套练习、数据结构、算法入门、算法进阶。本文梳理了这五个大项的思维导图,在下文会有详细介绍。
    ( a/ j- O. U5 }2 k7 _  希望各位能够找到自己的定位,通过自己的努力在算法这条路上越走越远。
    $ `" M" ~9 G$ m. ?5 G1 f  刚开始切勿心浮气躁,千万不要给自己立 flag,说一定要把这么多东西都学会。就算你的精力旺盛,日夜操劳,时间也是有限的。所以,首先是明确我们要做什么,然后制定好一个合理的 目标 ,再一点一点将要学习的内容逐步付诸实践才是最重要的。8 F: Z+ j( [8 U
      每日一篇C语言打卡,目前更新到:光天化日学C语言(20)- 赋值运算符与赋值表达式 | 让代码变得更加简介(建议收藏)。
      a! K! X2 F: u8 q; r
    0 t. z- H7 n( {* L2 ^; M6 R

    ; O8 }9 }' J$ s( V- S; z2 c* d
    3 Y/ I& g5 [  ~0 q$ A4 Y1 T

    5 Q( A* _5 ^" ^9 X' a
    & Q$ A* q* L. J3 ~& v
    ; h' c' ]5 u& P  S* I4 X5 o
    5 m& A4 Q5 d- U  Z( T3 E

    % c4 D; ]& \: o3 L2 m4 s2 E2 J, c2 ~# v图片较大,文章中有拆解,需要原图可以留言找我要哈) t/ e; Y  L/ l9 N* w
    1、基础语法学习
    & v' R$ R6 b! H  e; u算法是以编程语言为基础的,所以选择一门编程语言来学习是必须的。! D+ B7 s& C4 ?/ z0 k4 L4 V
    因为作者本身是C/C++技术栈的,所以就拿C语言来举例子吧。如果是 Java、Python 技术栈,可以跳过 C语言相关的内容。这一小节,先给出学习路线图,然后我再来讲,每部分应该如何去学。6 S9 }- M8 B8 d- Q0 z6 R0 U

    ! M9 d/ H1 a! I  ^

    8 M( W* L5 {0 p: Q8 Y( e( e; k7 p' c: W9 Z

    2 k2 }0 C* h4 c/ B' Z3 o1)HelloWorld
    . l. u' u; z( ?9 \' @  B6 s* _无论是 Java、Python、C/C++,想要上手一门语言,第一步一定是 HelloWorld,先不要急着去配环境。如果环境配了几个小时,可能一开始的雄心壮志就被配环境的过程消磨殆尽,更加不要谈日后的丰功伟业了。: g, ~# k. O/ Z, w2 p; K# q
    2)让自己产生兴趣$ _; D. s6 y! C. K3 Y
    所以,我们需要让这件事情从一开始就变得 有趣,这样才能坚持下去。比如找一个相对较为有趣的教程,这里我会推荐这个:《光天化日学C语言》。听名字就比较搞笑,可能作者本身也不是什么正经人,哈哈哈!虽然不能作为一个严谨的教程去学,起码可以对搞笑的内容先产生兴趣。从而对于语言本身有学习下去的动力。
    $ W  c3 t% e' W刚才提到的这个系列,可以先收藏起来。回头再去看,它讲述的是 对白式 的 C语言教学,从最简单的输出 HelloWorld 这个字符串开始讲起,逐渐让读者产生对C语言的兴趣。这个系列的作者是前 WorldFinal 退役选手,一直致力于 将困难的问题讲明白 。我看了他的大部分教程,基本都能一遍看懂。算了,不装了,摊牌了,因为我就是这个作者。
    9 h8 D" S/ p) W% G# A: c. l! N1 G1 ^3)目录是精髓
    2 m, K, i9 F8 R+ _# ]! v- s然后,我们大致看下你选择的教程的前几个章节,那些标题是否有你认知以外的名词出现,比如以这个思维导图为例,前几个章节为:- Q! i# F% g0 S
    1、第一个C语言程序
    1 `( ~& K5 ]: \2、搭建本地环境
    9 U1 [8 `& |# w3、变量
    " W5 b, y$ A7 Z4 L7 a( U4、标准输出! D, {3 ^% c0 Y1 |
    5、标准输入4 w& u- p9 x, q' x$ k) q' _6 `5 N
    6、进制转换入门
    8 F& Y; |- t9 i& S. z' D) V9 K1 k7、ASCII字符
    ! P5 T' f2 `8 N) d7 M8、常量
    6 |# P: [3 l; @, W4 A
    : `4 q$ J% T1 r8 d* q
    / A1 R; ?& r' `' R7 \
    如果你觉得这些名词中有 3 / 4 以上是没有什么概念的。那么,可能需要补齐一些数学、计算机方面的基础知识。反之,我们就可以继续下一步了。* q7 ^0 \9 d2 c, [
    4)习惯思考并爱上它
    ( l! a/ v: |, T4 G" C只要对一件事情养成习惯以后,你就会发现,再难的事情,都只是一点一点积累的过程。重要的是,每天学习的过程一定要吃透,养成主动思考的好习惯。因为,越到后面肯定是越难的,如果前期不养成习惯,后面很可能心有余而力不足。
      e2 t0 D1 }" b$ }7 z6 o就像刷题,一旦不会做就去找解题报告,最后就养成了看解题报告才会做题的习惯。当然这也是一种习惯,只不过不是一种好习惯罢了。
    5 |1 q0 D0 C, c" y0 J- K- C5)实践是检验真理的唯一标准# d$ L+ @: W  u; v  p/ g. i1 b
    光看教程肯定是不行的,写代码肯定还是要动手的,因为有些语法你看一遍,必定忘记。但是写了几遍,永世难忘。这或许就是写代码的魅力所在吧。
    ; p1 m! i; I. ]! k" p. |; S所以,记得多写代码实践哟 (^U^)ノ~YO/ M6 i4 J7 m" P, e2 b( E
    6)坚持其实并没有那么难  m, h: D8 L5 {) o
    每天把教程上的内容,自己在键盘上敲一遍,坚持一天,两天,三天。你会发现,第四天就变成了习惯。所以坚持就是今天做了这件事情,明天继续做。
    * b1 z- O, ]% A7)适当给予正反馈
    4 r6 @- ~6 `: [) R: D9 R% {然而,就算再有趣的教程,看多了都会乏味,这是人性决定的,你我都逃不了。能够让你坚持下去的只有你自己,这时候,适当给予自己一些正反馈就显得尤为重要。比如,可以用一张表格将自己的学习计划记录下来,然后每天都去分析一下自己的数据。
    " ^* N3 w1 P  e& }( w当然,你也可以和我一样,创建一个博客,然后每天更新博文,就算没有内容,也坚持日更,久而久之,你会发现,下笔如有神,键盘任我行!更新的内容,可以是自己的学习笔记,心路历程 等等。7 O% ?$ @: x+ i4 J* X
    看着每天的粉丝量呈指数级增长,这是全网对你的认可,应该没有什么会是比这个更好的正反馈了。
    ! x( J3 n3 n  @! f! C$ C% c8)学习需要有仪式感) H' M7 X% C) n! b( |- @- F/ S
    那么,至此,不知道屏幕前的你感想如何,反正正在打字的我已经激情澎湃了。已经全然忘记这一章是要讲C语言基础的了!
    1 g; _, e% L2 o介于篇幅,我会把C语言基础的内容,放在这个专栏 《光天化日学C语言》 里面去讲,一天更新一篇,对啊,既然说了要坚持,要养成习惯,我当然也要做到啦~如果你学到了哪一章,可以在评论区评论 “打卡” ,也算是一种全网见证嘛!9 T$ v) [9 o0 f' Q. r% c
    我也很希望大家的学习速度能够超越我的更新速度。" _1 |# L, b/ [( Y# }1 M
    2、语法配套练习
    : ?) f1 X- n9 D% X. |  X, @9 c5 a学习的过程中,做题当然也是免不了的,还是应征那句话:实践是检验真理的唯一标准。) s, e- k& s  N
    而这里的题库,是我花了大量时间,搜罗了网上各大C语言教程里的例题,总结出来的思维导图,可以先大致看一眼:4 L, V7 s" C# Y
    ( u- T5 G! o2 m! I4 v+ T
    ) s2 t; ^" n6 I. D- c
    6 M) Z, M; d  D5 M1 s
    - W* ]( A4 j6 ~+ Y; {3 K
    从数学基础、输入输出、数据类型、循环、数组、指针、函数、位运算、结构体、排序 等几个方面,总结出的具有概括性的例题 100 道 《C语言入门100例》,目前还在更新中。
    / w6 f7 T! i$ E6 T1 p这里可以列举几个例子:
    " z' P$ i* t3 T: U+ g7 w6 \/ `1、例题1:交换变量的值$ W+ X; |4 h  N8 t$ Y
    一、题目描述
    7 T- D* S9 {, e, f( ~5 t1 |" h  循环输入,每输入两个数 a aa 和 b bb,交换两者的值后输出 a aa 和 b bb。当没有任何输入时,结束程序。
    ) |. Y$ y1 c9 O4 W" ^  P, `& L0 e- a
    4 f- N! y6 G" w& {( R* L* y# Q

    " q9 W, y3 f6 C& ]; V/ ]1 d: g- g
    3 h% K( Y& t% W7 r4 h9 E) W, Y
    二、解题思路
    5 u' {) v0 h0 D' p) Z" F难度:🔴⚪⚪⚪⚪
    : O: x) ]; o' q& B
    . f& h9 x  D$ y7 R
    , w* j. M2 g4 {' o9 @* G4 y0 }1 @
    这个题的核心是考察如何交换两个变量的值,不像 python,我们可以直接写出下面这样的代码就实现了变量的交换。* @" Z% Y+ u2 h1 G4 S) D) W
    a, b = b, a5 o# |$ H4 c8 u  X& i
    19 N3 v0 D% a( b8 i
    在C语言里,这个语法是错误的。
    ! b# h: f2 B# b3 s9 v我们可以这么理解,你有两个杯子 a aa 和 b bb,两个杯子里都盛满了水,现在想把两个杯子里的水交换一下,那么第一个想到的方法是什么?
    * ~7 B$ z, d; M( z, j1 h当然是再找来一个临时杯子:9 Z  `9 o& c3 ?- p: T* H
      1)先把 a aa 杯子的水倒进这个临时的杯子里;# B+ d& M4 N( U' J6 W
      2)再把 b bb 杯子的水倒进 a aa 杯子里;
    * D& ~/ q/ |- N' b5 ^  3)最后把临时杯子里的水倒进 b bb 杯子;1 G! m2 N3 B# E+ i5 ], R

    " X3 D, U# T+ v7 W% Z

    ' e: _' W* Z( L4 f! n4 [+ ?这种就是临时变量法,那么当然,还有很多很多的方法,接下来就让我们来见识一下吧。, I& |; `/ e: g% o  l$ l

    $ L% t8 W+ X3 K! f3 Y
    ( R8 R- D% }9 `! h9 h! p1 u
    三、代码详解8 Y/ I3 |& {8 f$ ^$ z
    1、正确解法1:引入临时变量1 L$ u  M2 I" K2 o& k. `/ E
    #include <stdio.h>) ^: S+ b( F" v; {, W+ E% D5 g( a
    int main() {
    1 V8 s5 _. |6 e7 c% b0 c+ Z3 g2 r    int a, b, tmp;& H7 O8 v2 M; |
            while (scanf("%d %d", &a, &b) != EOF) {
    , D; ]8 a$ v4 m  [( C! b            tmp = a;   // (1)
    & E' [! \) a4 s- C5 p3 w, h            a = b;     // (2)6 `& \4 _/ S/ B3 _) u! }! P2 S
                b = tmp;   // (3)/ ~/ j  R# N# {$ ~! Z
                printf("%d %d\n", a, b);
    + a+ V1 s6 R* S! U0 _7 B1 O        }
    & D+ C: b1 I) q( \3 J        return 0;; N) w* x2 [; Y  B$ H
    }
    1 H$ M8 |! a+ E4 X1# ~9 v  Q! I( e. i
    2
    0 j( x; U) O7 r) P' q3: I3 h+ i6 e" z1 j
    4
    / [% |4 h7 Z- X1 l7 n5# D. q! t: j" f- E5 k- F9 ]
    6
    9 A! j$ `) J# ^  ?( p8 ]7
    # v: _* l$ ^- Q9 ]2 |8) w  d! e6 Q$ N" ?; g6 }" w
    9
    " k6 I2 K  E7 q7 ]; a109 p# ~# o$ M( k
    11; A1 D1 V+ H9 T4 w! }' o, X5 ]$ t! J" y1 D
    ( 1 ) (1)(1) tmp = a;表示把 a aa 杯子的水倒进这个临时的杯子里;
    ! T/ M) c# r2 i- x0 F5 W% }( 2 ) (2)(2) a = b;表示把 b bb 杯子的水倒进 a aa 杯子里;* w7 V% P2 ]& E4 j: }8 m# @
    ( 3 ) (3)(3) b = tmp;表示把临时杯子里的水倒进 b bb 杯子里;
    ) Q, W, M/ U2 _这三步,就实现了变量 a aa 和 b bb 的交换。
    + y  y) R& o+ \' m1 L$ y( U+ F5 N2、正确解法2:引入算术运算$ f& f! L5 X+ ?  K, ^3 c1 E$ Q. b  h6 X
    #include <stdio.h>+ m$ _9 E! H. v4 r9 Z! r2 _$ y, }
    int main() {! s4 J. g, H6 `! d$ z) |: [
        int a, b;. O+ Q; D9 w" C% O2 j6 C5 [
            while (scanf("%d %d", &a, &b) != EOF) {8 i( }5 P$ w0 h9 ?6 r3 D
                a = a + b;   // (1)* F/ Y" d. }7 a7 c* `) w- Y+ |
                b = a - b;   // (2): J" z. y0 b) }" q0 |! ]% z( X
                a = a - b;   // (3)- [) s/ G) r3 @1 i2 \3 H
                printf("%d %d\n", a, b);
    ( h0 ~% G( p% _        }5 f9 y3 a* N8 x9 C+ I4 j2 f& h
            return 0;
    5 b, Y/ s2 f/ X1 s8 f6 ~}
    : \7 d$ D" Y# H; U. z) F' R1
    % K) X7 P8 c9 w* S2
    ; E% [7 u. x! q5 C# p0 T: q3. x. h5 y/ F: r
    4) V  T. T9 D+ Z6 H% f- z! x% X3 y1 n
    5- D. x- ]/ A4 c! l
    66 L1 L6 Q- p& C+ l
    7
    8 `# j) Y& Z- h6 Q" B8
    , v8 D. i1 T7 M1 }9
    ; U6 g% ^0 M7 O* `2 \  C10/ R3 X- t: |2 a. V2 K
    11  X6 ^( O3 a( r* Q
    ( 1 ) (1)(1) a = a + b;执行完毕后,现在最新的a的值变成原先的a + b的值;  m$ u+ O( Z1 d7 }; s& K
    ( 2 ) (2)(2) b = a - b;执行完毕后,相当于b的值变成了a + b - b,即原先a的值;& c, J" k9 S4 K/ j0 R
    ( 3 ) (3)(3) a = a - b;执行完毕后,相当于a的值变成了a + b - a,即原先b的值;
    " O9 z2 _9 R1 S$ `从而实现了变量a和b的交换。
    4 J  U6 s& h) K/ u' ~- T+ p9 M+ P3、正确解法3:引入异或运算
    + F$ K+ E- I' p$ h  R首先,介绍一下C语言中的^符号,代表的是异或。$ D& Z; z- b7 }) ]( R
    二进制的异或,就是两个数转换成二进制表示后,按照位进行以下运算:
    , F6 Q3 ]3 j/ n& F左操作数        右操作数        异或结果
    * h1 p( q# _* O. F4 P- N! [! m: u' _0        0        0
    6 n; @9 Z" c- l+ j/ f1        1        0
    ! F! R  c; m3 d8 l0        1        16 ~0 L& p5 S' P6 T2 k
    1        0        1/ Q/ u. p  L" _* B1 ~5 d
    也就是对于 0 和 1,相同的数异或为 0,不同的数异或为 1。
    ) M8 S# `1 x3 k, C$ A$ g0 `这样就有了三个比较清晰的性质:
    # x+ m0 A0 b4 K1)两个相同的十进制数异或的结果一定位零。
    + X$ H( t2 g: I8 o  u7 ~$ T; m2)任何一个数和 0 的异或结果一定是它本身。9 Z7 a3 v+ N9 _
    3)异或运算满足结合律和交换律。; {8 e1 T5 v- e3 |8 i4 f
    #include <stdio.h>
    # u2 H' a9 i, j7 p1 Y/ \9 M3 _+ j8 Xint main() {: g/ m# {8 }& r7 e
        int a, b;( V7 X4 ?7 u5 A# l
            while (scanf("%d %d", &a, &b) != EOF) {8 |* w: z( E5 D
                a = a ^ b;   // (1)# e: l$ R/ t% E/ c
                b = a ^ b;   // (2)
    ' n  N4 C3 W7 \' @            a = a ^ b;   // (3), r: y" A' @! D9 p5 Q) i
                printf("%d %d\n", a, b);
    " m0 S# |8 T3 H. d+ D        }
    & J$ @0 E6 |( b        return 0;1 b& {: I# l+ V) |! o2 r
    }% q6 f" _, c3 L( k+ C& T
    1
    4 T* n) T) G. Q) S6 G4 j2$ X6 G6 T& x! ~4 T6 n
    3" a; c( q5 H& T1 j
    4+ V# z7 g/ u0 m9 `7 s* I
    5
    * }, D9 d! E  m) ?, C% ]2 |68 o3 B" d- K3 c7 W/ w
    74 m* L) D/ d/ G. q% M
    8( Y, U9 v' ^$ [% ^6 J
    9
    " N$ @- v0 `8 i" R/ C; n  t10
    2 P; U+ X' Q0 Z2 M$ f; W11
    : K% |' r) L; [: t/ ?7 Y7 L2 Q3 B$ v我们直接来看 ( 1 ) (1)(1) 和 ( 2 ) (2)(2) 这两句话,相当于b等于a ^ b ^ b,根据异或的几个性质,我们知道,这时候的b的值已经变成原先a的值了。6 ~: W8 e7 n5 U# n
    而再来看最后一句话,相当于a等于a ^ b ^ a,还是根据异或的几个性质,这时候,a的值已经变成了原先b的值。7 t! a1 j' M, ~; o, O5 p, @8 ?
    从而实现了变量a和b的交换。
    * t# }3 U& k: {9 r0 u
    8 V' Z/ P* m% K/ b: j4 ~
    1 _- ]9 G! h1 Q: L- {/ ]  B
    4、正确解法4:奇淫技巧
    3 W" d! s9 \& q" }$ H1 N- }+ E9 @当然,由于这个题目问的是交换变量后的输出,所以它是没办法知道我程序中是否真的进行了交换,所以可以干一些神奇的事情。比如这么写:: M* G# L5 v( N- A$ L9 x
    #include <stdio.h>
    2 y# ?2 c; W4 T' V6 j2 W# fint main() {7 d" c+ Z1 r$ W4 G4 y7 T- O3 w  d
        int a, b;# Y9 s, f) v( k' }9 _8 Q& h3 }1 u
            while (scanf("%d %d", &a, &b) != EOF) {. V3 Q/ Z, s- O
                printf("%d %d\n", b, a);( s3 Y; C2 K9 H  m! e4 D. j3 G$ n  n
            }
    $ Q* O& j+ ^( v7 H7 U        return 0;
    ! ^1 m) R3 u; S}) V/ T3 h$ C% z6 n1 O
    1+ ]7 i% I# K) W. E0 l% P. @0 i
    26 N$ @% T0 i: l
    3
    ( t  f9 N: e+ ]2 G! z8 o' w" }4" ?/ z; M' K4 {& }- w4 N7 z8 Z
    5
    & H/ `8 ^! i0 `, W* n% r6 D$ b61 {- ^8 }! a% r$ t0 `. z8 ^
    7
    . X+ d$ y& `( g  |7 J86 Q1 N1 U2 s) e5 e( p! x* D
    你学废了吗 &#129315;?- ~1 E3 D6 m& H6 a) x7 q/ ]
    2、例题2:整数溢出& X6 g# m( F+ V4 l
    一、题目描述
    6 J$ K' P* {- w2 \# A9 ]+ @  先输入一个 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
    & `% a0 G8 f5 ~# }" X62" E  l' Y" s" A7 d
    ),输出 a + b + c + d a+b+c+da+b+c+d 的值。
    " `) A! I, G% S' K
    , d8 j& X+ n% [: H$ B; L0 P
    0 Y  j8 Z3 Z1 T3 G
    二、解题思路$ A! ~$ V* ]8 D+ P3 S4 \  ~
    难度:&#128308;&#128308;⚪⚪⚪! w) O! d/ L; N

    6 v# l: c# {& X, @
    . k& U/ H8 t( J/ X! h$ ]: i$ s5 V
    这个问题考察的是对补码的理解。
      b7 I' o: s# O$ f7 T仔细观察题目给出的四个数的范围:[ 0 , 2 62 ] [0, 2^{62}][0,2 ! n+ K, K  s" {  l8 A, B
    62
    ) u8 W0 D4 O. ?  n# z6 j* Q ],这四个数加起来的和最大值为 2 64 2^{64}2
    5 z! v8 R! k1 D2 ~$ L) ~64
    2 j/ b0 p" O% U% w 。而C语言中,long long的最大值为:2 63 − 1 2^{63}-12   x- A0 w1 L: j+ ?$ h( B$ |/ L7 s
    63" R- m) I) K& Y. u3 u, D( ?
    −1,就算是unsigned long long,最大值也只有2 64 − 1 2^{64}-12 # U6 c; [# E+ f6 F8 h/ q# G+ E$ M9 {* y
    643 {* w0 K) m  z: p6 {2 Z1 A
    −1。/ k1 s1 z6 T& [
    但是我们发现,只有当四个数都取得最大值 2 62 2^{62}2
    6 {4 T4 `1 H$ D/ |% S+ D62) Z. S8 {- _, A7 Y# x
      时,结果才为 2 64 2^{64}2
    ; `5 k9 b7 ]4 m64) B9 ~  R* Y! @- F5 H; R
    ,所以可以对这一种情况进行特殊判断,具体参考代码详解。* V8 @' J  S1 q% n0 ]6 `! ^( }0 s6 u
    三、代码详解
    - s5 M1 j) W1 E7 A# E( T" L" B#include <stdio.h>
    * F6 f0 f7 q3 ctypedef unsigned long long ull;                           // (1); }+ B4 P) g9 c2 C, M
    const ull MAX = (((ull)1)<<62);                           // (2)
    % e& D: M8 c, {9 h$ H
    4 P! \. f9 E8 f/ |0 M
    5 s, }, X" L% F, |5 ^& n
    int main() {
    6 s7 w' ]9 @" {& H$ K" ]        int t;% @+ a3 v& A. Q- H
            ull a, b, c, d;
    * N$ h1 A- Z8 v- k) C        scanf("%d", &t);" e$ p  n# R8 J
            while (t--) {
    ) I' }) O6 m( O# U! ^* v                scanf("%llu %llu %llu %llu", &a, &b, &c, &d);     // (3)
    ) `5 \0 W* L# l7 [                if (a == MAX && b == MAX && c == MAX && d == MAX) // (4)
    7 u) W: u1 z' @& o5 ?                        printf("18446744073709551616\n");             // (5)+ c, ~. h  c, }8 B! I! V' t
                    else
    2 Z  q& _. l# t* |. c                        printf("%llu\n", a + b + c + d);              // (6)
    % d+ U8 `4 A$ r% _( A        }' G9 f. X" p6 l* E, e3 J* P* A
            return 0;" u7 }% M# i3 _+ m. i
    }9 D( ^0 k6 \7 O1 q) f
    1
    $ C% z$ m% {, x6 A5 Z2
    # @( F" U5 G' U" B) r: Q3
    9 T- @* T& J, |: o* b4
    ; l9 _7 |9 `% @5
    2 `' m. S) `5 Y  d5 u& e- o7 P/ y2 V6
    ' z: O" q) C3 f4 V7
    0 k2 W  j6 j" W/ A! t( q$ {0 Y8
    1 {9 }5 A# h4 t- x90 \+ \7 G* l- I, P
    10, u. w. I/ y' k6 x* f% ^7 o
    11  s' ~& W% R* }- _: f
    12
    4 p/ S) Y3 G+ w13
    % l: R5 a' _. Y3 w7 E; S; J14+ z2 V: T5 t# a0 J9 \. q
    15
    * \4 f4 l( Q; |+ P- D; _16. R1 y* p+ i6 r! R, Y; n
    17& |" m1 h5 b3 h/ R6 j
    ( 1 ) (1)(1) 由于这题数据量较大,所有数据都需要用64位无符号整型。ull作为unsigned long long的别名;* E6 `& E4 |2 f7 a# p/ N4 ?
    ( 2 ) (2)(2) 用常量MAX表示 2 62 2^{62}2 * @9 K- T6 q+ v' y" w9 d; c( x
    62( z( `9 b- P* W5 A+ G6 d
    ,这里采用左移运算符直接实现 2 22 是幂运算;5 V; v5 D$ n$ L% v' p4 v
    数学        C语言
    5 h' [0 S6 @. Y, z0 K5 v4 A% n2 n 2^n2 2 x, x2 I1 q/ B# Q
    n) |# [2 D# |- u) z- ?3 D- b
            1<<n
    ( V2 `" H$ q$ B/ d; {需要注意的是,由于 1 是int类型,所以需要对 1 进行强制转换。(ull)1等价于(unsigned long long)1;
    - a6 g" w4 l7 F5 N# ]$ F( 3 ) (3)(3) %llu是无符号64位整型的输入方式;& z# V4 G4 I, |5 y- `" P
    ( 4 ) (4)(4) 这里是对所有数都等于最大值的特殊判断,&&运算符的优先级低于==,所以这里不加括号也没事;
    $ L/ E- y7 m- N- l9 C9 w( 5 ) (5)(5) 由于 2 64 2^{64}2 " u4 Z" s' [6 t  V( m2 u/ l
    645 f$ j( i; M# k/ U
      是无法用数字的形式输出的,所以我们提前计算机算好以后,用字符串的形式进行输出;
    ' G3 n! Z1 Z8 W, N1 Q& _1 }( ~( 6 ) (6)(6) 其它情况都在 [ 0 , 2 64 − 1 ] [0, 2^{64}-1][0,2 3 o8 |! S0 |3 g
    649 z" I) d& A* \8 Y  h8 j
    −1] 范围内,直接相加输出即可。
    + a( k0 t7 b  g* Q# i& }1 \由于这个专栏是付费专栏,可能对学生党不是很友好,所以作者经过再三思考,打算放出 300 张 一折优惠券, 先到先得。只要拿这个图片来找作者即可享受,仅限前 300 名。
    6 P/ y' f9 K' N, Q5 x为了适当提高一定门槛,你至少需要学会如何下载图片或者截图并且发送到微信里 &#129315;。
    * ~9 x$ U% l* u! ?4 t) V  m
    : _2 K0 ~$ H! K3 B+ Z8 z/ ~, Y5 V/ l  o
    0 U4 K  V/ |1 R) `; s0 _. Y
    3、数据结构; C0 r& p) c  k0 v9 ~7 Q7 _/ n
    《C语言入门100例》上的例题,如果能理解前面 25 道,那基本C语言的学习就可以告一段落了,接下来就要开始我们的数据结构的学习了。
    : z, v0 K2 @  x! X' F" F1、什么是数据结构; H: X; ^) W7 ^" _; E$ l
    你可能听说过 数组、链表、队列、栈、堆、二叉树、图,没错,这些都是数据结构,但是你要问我什么是数据结构,我突然就一脸懵逼了。! E1 W- B+ @" e2 g
    如果一定要给出一个官方的解释,那么它就是:
    9 R" C2 u( ?* h( M& X计算机存储、组织数据的方式。相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。往往同高效的检索算法和索引技术有关。+ [5 \6 E5 e7 H( K
    : {4 e4 L1 N( o- p9 |) b5 e! M# v
    2 y" q+ C4 S/ S! }6 c2 ?0 r
    是不是还不如说它是堆,是栈,是队列呢?
    2 i* O! s' W8 n' ^. I6 \是这样的,我们学习的过程中,跳过一些不必要的概念,能够节省我们更多的时间,从而达到更好的效果,当你还在理解数据结构是什么的时候,可能人家已经知道了栈有哪些操作了。/ J( u& w% i4 U- [2 E! G
    2、数据结构和算法的关系
    6 u2 e0 I! x, X" G- \% A很多同学搞不明白,数据结构与算法有哪些千丝万缕的关系?甚至有些同学以为算法里本身就包含了数据结构。$ ]4 S  i! W7 N  m7 ~, M) h: E
    数据结构主要讲解数据的组织形式,比如链表,堆,栈,队列。
    ; X: g  ^' y; b而算法,则注重的是思想,比如链表的元素怎么插入、删除、查找?堆的元素怎么弹出来的?栈为什么是先进后出?队列又为什么是先进先出?1 u* A! ?* y& `/ @5 X" l1 |8 a
    讲得直白一点,数据结构是有实体的,算法是虚拟的;数据结构是物质上的,算法是精神上的。当然,物质和精神 缺一不可。
    4 _' A8 y6 B& R% X" u5 C9 Q* |* r& V3、数据结构概览
    9 |9 z! }( ?' ^* U, B周末花了一个下午整理的思维导图,数据结构:% n  R, |* E0 K; i3 y
    & v5 U! `6 Y0 h! _' r

    ' t4 a2 q3 I* N' R: A常用的一些数据结构,各自有各自的优缺点,总结如下:+ P$ Z2 P. J  i
    a、数组
    % V& g: s- W7 b9 ]& B1 D+ H内存结构:内存空间连续
    ) A3 v4 ]3 U2 O/ H实现难度:简单+ I+ W  ^2 M, k
    下标访问:支持
    4 G3 P: v9 f1 _分类:静态数组、动态数组# C' N) a8 B9 F& ?( ^* E. J
    插入时间复杂度:O ( n ) O(n)O(n)
    ( D( ?8 I  }- {2 ^' O  e) a. F查找时间复杂度:O ( n ) O(n)O(n)
    ) ]7 ^8 Q! _+ D1 |删除时间复杂度:O ( n ) O(n)O(n)
    / ~8 t4 U) I( h9 V& Q7 C3 M  r9 ]
    ) t. t' ?2 \$ j- f3 `
    4 a5 R+ p* \8 `" I4 l
    b、字符串
    6 E2 l7 f2 ?- s$ N, v/ @& d内存结构:内存空间连续,类似字符数组
    ! [, U, l& Y5 i: Y& m" {  E$ B实现难度:简单,一般系统会提供一些方便的字符串操作函数2 F- Q# _, V! @1 E( I' p: @
    下标访问:支持
    / X' T3 M3 J4 j5 y( `6 v插入时间复杂度:O ( n ) O(n)O(n)
    ( O5 j: u+ U3 x* ~! v3 j  I查找时间复杂度:O ( n ) O(n)O(n)
    $ ^* K+ _+ F7 u删除时间复杂度:O ( n ) O(n)O(n)8 c) f- I  h, b2 L% J: t2 [0 J

    1 S$ O9 M. [) X1 {8 Z

      q1 d8 i; t7 i3 r7 V5 T9 Fc、链表
    4 D# _% n. ]$ b# ^内存结构:内存空间连续不连续,看具体实现
    : ^7 o( D7 E1 k实现难度:一般
    3 s4 j! S) \8 Y下标访问:不支持
    / L( U3 v2 B7 r! N/ ?8 w分类:单向链表、双向链表、循环链表、DancingLinks4 n4 h: M* D3 J1 v) [8 t( y
    插入时间复杂度:O ( 1 ) O(1)O(1). P4 j" Z7 }8 I) `0 A4 y! R
    查找时间复杂度:O ( n ) O(n)O(n)
    9 r. d) Z* R1 q  L9 @6 G% M删除时间复杂度:O ( 1 ) O(1)O(1)" l8 ~7 k$ }1 U

    3 P  w3 ], c- }* }

    % f; z, F, S4 Hd、哈希表% s: F4 P' c4 z5 C8 g6 t" n! Y
    内存结构:哈希表本身连续,但是衍生出来的结点逻辑上不连续
    9 @) }- D. B2 m  [实现难度:一般  P4 A8 u) C/ c! i; c
    下标访问:不支持5 X* b6 o' j  \) f
    分类:正数哈希、字符串哈希、滚动哈希
    ) A* T2 X, z) j插入时间复杂度:O ( 1 ) O(1)O(1)% s, g' }/ N0 C+ t3 Z3 I5 Y
    查找时间复杂度:O ( 1 ) O(1)O(1)
    3 g: k3 R7 x! e& x4 o: v删除时间复杂度:O ( 1 ) O(1)O(1)4 y% p6 T) M* U$ i; F+ d

    3 o/ }! S0 n6 `' W$ J

    3 U! T$ o# ]* J' P* Ee、队列/ H8 ^& J  x8 V5 K8 G3 e
    内存结构:看用数组实现,还是链表实现
    1 s' s5 q4 s+ k* V. o, s实现难度:一般! J" w1 T6 A+ _. U4 l/ |+ d2 v
    下标访问:不支持
    - f$ a6 e' ], n# b# r分类:FIFO、单调队列、双端队列
    & P" p4 n7 W" D: p8 F插入时间复杂度:O ( 1 ) O(1)O(1), Z5 B. ~) d. v; e5 }. Y( d
    查找时间复杂度:理论上不支持
    : d* W5 W" X2 k3 I. {5 r( l删除时间复杂度:O ( 1 ) O(1)O(1)
    " i6 ~  E. ?: z" E( K/ G
    7 R0 G  m1 w0 w* c3 S
    % j; m% d( ~- X0 A5 H
    f、栈
    # ^! Q- i- L: X5 o' M" E9 Z* h2 `内存结构:看用数组实现,还是链表实现
    ' ?. H% h& ~0 l. j6 ?) ~% O0 m实现难度:一般: H5 j) Y1 O6 V) q5 B, K7 ~7 ~0 v
    下标访问:不支持
    2 j: O' Y7 e2 _4 t7 y# Q' h分类:FILO、单调栈% u+ m( y0 l% ?- Y* v. `1 h
    插入时间复杂度:O ( 1 ) O(1)O(1)
    : c  S7 Z# E% `3 F) J7 H! ^6 b查找时间复杂度:理论上不支持7 j+ h6 W2 I  y% ^% r  O* O
    删除时间复杂度:O ( 1 ) O(1)O(1)3 }& g3 C2 N0 V  }: a5 f* G3 a
    6 m7 v* S( g& w) M
      c  i3 L4 x: G. i4 ?
    g、树& A* @; J0 B+ N' K- u
    内存结构:内存结构一般不连续,但是有时候实现的时候,为了方便,一般是物理连续,逻辑不连续
    4 S( H9 k- V* E& J, u5 n  d# w实现难度:较难
    3 I9 }7 c  F% K6 X; l4 }下标访问:不支持* j+ Z: Y" H+ w$ a* k
    分类:二叉树 和 多叉树
    , L: d" E/ ^( i+ V6 r7 {/ N% u插入时间复杂度:看情况而定
    $ q! _# U' \) Z. n8 ?  N" g查找时间复杂度:理论上 O ( l o g 2 n ) O(log_2n)O(log : H& u. p7 h3 Z4 ~
    2
    5 H$ C6 s! {2 r9 S9 ^4 x  t+ N​        9 d! {4 K: w$ c# r
    n)
    9 c4 n6 Z% _: O' |  Y删除时间复杂度:看情况而定
    % y! N- d: N6 z- A8 F$ {" L8 _: @6 e* ], v$ {+ g

    ! A, P8 _$ ^4 b. t1、二叉树
    ! ^( o. v* s+ @$ X, T2 U% G二叉树的种类较多,比如:二叉搜索树、平衡树。平衡树又可以分为 AVL 树、红黑树、线段树、堆。最平衡的树莫过于满二叉树了。0 B/ a/ H0 V7 p' ~  |$ Y
    其中,堆也是一种二叉树,也就是我们常说的优先队列。5 ^+ N: x& ~) m9 A
    2、多叉树& i/ s4 i. w  j' |/ C# N5 W
    B树和B+树是多叉树,当然我们平时学到的并查集其实也是个多叉树,更加严谨一点,应该称之为森林。9 @' T% [2 c; ~/ y
    h、图
    $ P5 A6 q0 }* c4 [' k- T- S+ }内存结构:不一定/ `0 V+ r2 q  t% S( E/ b- @
    实现难度:难
    : m4 ]/ \' G2 \! O( ]3 e" Y% q( a下标访问:不支持
    3 N7 d' c! L& |1 D& U( O分类:有向图、无向图6 u) `  z* L$ |6 x" e
    插入时间复杂度:根据算法而定% M2 c6 Y' R' m6 C$ ], r
    查找时间复杂度:根据算法而定
    ) Q* q" H) R1 O1 w" b. n* f删除时间复杂度:根据算法而定+ v) o0 n) Y4 N/ U& h
    / `) r( X. z$ Y$ u& h
    0 Y1 \; p8 o. s1 s9 Z9 @
    1、图的概念6 Q' i# i) K8 a+ ^3 Z1 F" J
    在讲解最短路问题之前,首先需要介绍一下计算机中图(图论)的概念,如下:
    : R# y* V; ^: `9 ]" v$ c3 L' d9 k图 G GG 是一个有序二元组 ( V , E ) (V,E)(V,E),其中 V VV 称为顶点集合,E EE 称为边集合,E EE 与 V VV 不相交。顶点集合的元素被称为顶点,边集合的元素被称为边。( ]  {# ]! L& w
    对于无权图,边由二元组 ( 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 为权值,可以是任意类型。; B) {: H. z( w7 S, ^2 |
    图分为有向图和无向图,对于有向图, ( 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;
    ; R2 z, y6 r# i2、图的存储
    * t, v$ N6 j+ a4 ^: m* L( f9 U对于图的存储,程序实现上也有多种方案,根据不同情况采用不同的方案。接下来以图二-3-1所表示的图为例,讲解四种存储图的方案。
    1 f9 L) \( p% |  r) W- C
    3 R- _  s$ q8 Q% [! Q, M

    5 b& g5 W; k2 f1 W8 Y; V1)邻接矩阵+ \# w: e) j% B, P4 O( [2 G
    邻接矩阵是直接利用一个二维数组对边的关系进行存储,矩阵的第 i ii 行第 j jj 列的值 表示 i → j i \to ji→j 这条边的权值;特殊的,如果不存在这条边,用一个特殊标记 ∞ \infty∞ 来表示;如果 i = j i = ji=j,则权值为 0 00。3 `5 F$ ?: `3 k* b$ F! \
    它的优点是:实现非常简单,而且很容易理解;缺点也很明显,如果这个图是一个非常稀疏的图,图中边很少,但是点很多,就会造成非常大的内存浪费,点数过大的时候根本就无法存储。6 Z$ o* W0 k* V  a) w
    [ 0 ∞ 3 ∞ 1 0 2 ∞ ∞ ∞ 0 3 9 8 ∞ 0 ] \left[) s, p: P1 W* J  h- M: |
    01∞9∞0∞8320∞∞∞30
    7 |2 }" C) L' B; \1 J0∞3∞102∞∞∞0398∞0
    % e1 n+ E0 p2 [3 b4 b\right]& ^* f" f" f& W4 _( y3 S% T
    ⎣
    8 [, b7 i& S) K. [% Q& }, U. Z⎢
    % s. k+ S' k5 k& @9 q⎢( ^$ v* U- i# Y7 X
    ⎡- ]# H. l9 R6 V8 j
    ​       
    6 }7 ?4 o+ p  H* u  , h; Y1 M* f, ~! V- ^
    0& r5 o/ W6 r3 G6 N+ b; `
    1! D7 g5 g( b7 u6 ?/ O
    ∞
    3 u/ d  a, E& W) u9
    $ Y8 k/ l: ~: [2 _​        8 p, _4 C  A2 N# J# ~4 O1 y1 B
      
    + z6 V# B7 c! d! ]7 z* V9 j) i∞2 t0 U8 n4 r2 B& S3 i
    0
    - x& f' c# |2 m' L1 `∞
    & S! E- I5 n; q- q8# P0 H: Z9 k: F5 r$ W+ z+ {2 `
    ​        : D, z- ^; m2 m% _5 O- c+ x# _
      " `; |7 g% u- n& A+ g! j
    3
    3 G' @  h8 ?8 a8 E) H1 I; n$ Z20 U# i' h/ B4 f+ `
    0/ K  a% H  z, ?4 F
    ∞* r  n6 |" `4 Y7 }6 z! @
    ​        8 {/ |' w/ p" N4 V: A& R+ c
      
    / Z( `0 M: Q3 Z∞2 B/ d) P# S7 K3 T3 W6 m
    ∞
    + i* o0 N. D  t4 b& L! ]. z" S) m7 i36 E) L) f: x# i; ]
    0
    ' c  v1 X1 I9 w( \​       
    9 {0 E0 ~* ^3 r3 H  
      G/ f: Z! `% o3 O9 j& _⎦0 H7 o$ D0 ^* t8 R: r
    ⎥; x: v' m' s  q( j- v( C+ [" g, r
    ⎥2 x& y; h5 b5 c5 N3 {/ z
    ⎤1 o$ X7 \: C! E" C- d' ^3 I# p
    ​        * d6 m* Q4 T3 B3 M2 \- |; C! n
      U! K: _5 b# }$ w
    2)邻接表. d' w2 D, x5 p, s7 @% r
    邻接表是图中常用的存储结构之一,采用链表来存储,每个顶点都有一个链表,链表的数据表示和当前顶点直接相邻的顶点的数据( v , w ) (v, w)(v,w),即 顶点 和 边权。. e1 K. ^/ |! r, U6 m' n0 b; u3 [
    它的优点是:对于稀疏图不会有数据浪费;缺点就是实现相对邻接矩阵来说较麻烦,需要自己实现链表,动态分配内存。
    6 {$ n. Z% k  E. L8 k0 D- O: }& V( C3 w如图所示,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) 二元组。! C; y6 V6 W/ s
    8 w2 d6 S' w% L

    2 T4 S# m: b& u: W& k: W9 I6 E在 C++ 中,还可以使用 vector 这个容器来代替链表的功能;
    - m4 d' l/ v) g3 r    vector<Edge> edges[maxn];
    7 y# o$ e! ]3 D$ y( N1
    ! S* e( [7 b; j3)前向星
    7 ]3 o! a2 j" j" \5 t* T前向星是以存储边的方式来存储图,先将边读入并存储在连续的数组中,然后按照边的起点进行排序,这样数组中起点相等的边就能够在数组中进行连续访问了。
    ( {5 v3 N7 s- S) E/ V7 b. ~9 _它的优点是实现简单,容易理解;缺点是需要在所有边都读入完毕的情况下对所有边进行一次排序,带来了时间开销,实用性也较差,只适合离线算法。
    - i5 ~5 r$ y- g2 e4 N  X+ s如图所示,表示的是三元组 ( u , v , w ) (u, v, w)(u,v,w) 的数组,i d x idxidx 代表数组下标。
    7 {/ ?" [5 ]; a$ {- L; X" \; o0 _' Z( I( E# p

    ; g+ `, L- n+ V1 ]0 P9 t7 L那么用哪种数据结构才能满足所有图的需求呢?
    % Y2 Q* x  ?9 U7 E0 E% L/ Z9 Y接下来介绍一种新的数据结构 —— 链式前向星。
    ! A+ h; V( u' A. O, o4)链式前向星
    , H" T2 f' D+ @% `* @6 c链式前向星和邻接表类似,也是链式结构和数组结构的结合,每个结点 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 指向下一条边。
    1 s: p  c! p( i4 L9 H: U: `6 t具体的,我们需要一个边的结构体数组 edge[maxm],maxm表示边的总数,所有边都存储在这个结构体数组中,并且用head来指向 i ii 结点的第一条边。6 n, z  n9 W8 }- h3 `+ Y9 a
    边的结构体声明如下:
    2 {4 P; E+ M8 Q& O. pstruct Edge {
    " L9 g$ h7 |* ~# ~    int u, v, w, next;+ {$ b) Y0 f; L
        Edge() {}
    3 U) B$ T7 n/ G3 }* F7 p" Z    Edge(int _u, int _v, int _w, int _next) :1 K3 z8 }+ D5 b0 e7 ?* D7 V% _
            u(_u), v(_v), w(_w), next(_next) & }* {, ~$ r( p8 H7 ?
        {1 h6 L2 f: [1 h2 A
        }
    0 g7 I! O% x0 s. b& A9 p8 w+ a# ?}edge[maxm];: M% J8 D$ F5 P
    1% c( V& B# X( K7 C6 T1 ~  D4 I$ o
    2
    * d. R0 ^- {2 X  A3
    # Y' B/ R# @; L1 n: N6 k4, m+ @2 U6 c& R1 q$ Q5 i
    5
    9 Q! q8 e( G$ e. K7 a69 \6 e5 H. b  S" k- i1 n' a
    7
    ' e0 `8 A2 ?) m4 d+ m) ]8
    ( Z9 z+ p( T$ _初始化所有的head = -1,当前边总数 edgeCount = 0;" B" h. Z! l, ^1 Y7 i+ M' x
    每读入一条 u → v u \to vu→v 的边,调用 addEdge(u, v, w),具体函数的实现如下:+ ^/ b4 ]& [1 T8 P  x2 f; Y
    void addEdge(int u, int v, int w) {  f4 ]" p( ?- n! [0 m
        edge[edgeCount] = Edge(u, v, w, head);' c2 ]' o7 h* z" H  g- Z
        head = edgeCount++;0 M6 o  R: N6 z1 `2 T. |3 O- |
    }
    , Y0 T( F- d; B, A$ V3 b1- f' f4 u4 l* G5 j' |
    20 L1 D1 p# h! K* H5 N
    3
    3 J' s' C' R6 f7 P  X4  F* ?+ r, {; {
    这个函数的含义是每加入一条边 ( u , v , w ) (u, v, w)(u,v,w),就在原有的链表结构的首部插入这条边,使得每次插入的时间复杂度为 O ( 1 ) O(1)O(1),所以链表的边的顺序和读入顺序正好是逆序的。这种结构在无论是稠密的还是稀疏的图上都有非常好的表现,空间上没有浪费,时间上也是最小开销。2 P% e' N5 A  O; r5 z! K
    调用的时候只要通过head就能访问到由 i ii 出发的第一条边的编号,通过编号到edge数组进行索引可以得到边的具体信息,然后根据这条边的next域可以得到第二条边的编号,以此类推,直到 next域为 -1 为止。
    # I( p" t9 W' Tfor (int e = head; ~e; e = edges[e].next) {
    % d" \( x, u, d/ s4 c" U    int v = edges[e].v;
    & w+ W& Y+ N. z: c7 A6 m5 o( ^; K    ValueType w = edges[e].w;
    3 ]5 ], B5 H5 Q' I5 b( w- B7 }' N1 d    ...$ _- a  c5 F4 I  I% ?4 Y& j5 c* Q/ x
    }# U+ e# C" d7 z4 B! h( v; p, N: B/ ?
    17 p% ^" ^3 [4 c- p( x$ `/ p
    24 z! l1 m6 u2 D' v
    3
    * Z5 ~' Y$ c% l1 }# c/ I$ X. `4
    ! v9 H4 i  ]2 A2 t/ J8 b51 h6 G  w3 v4 U8 n' s
    文中的 ~e等价于 e != -1,是对e进行二进制取反的操作(-1 的的补码二进制全是 1,取反后变成全 0,这样就使得条件不满足跳出循环)。
    4 h1 B' Z% J9 d7 H; O4、算法入门
    7 Z$ O0 o5 k" Z) V) L0 j算法入门,其实就是要开始我们的刷题之旅了。先给出思维导图,然后一一介绍入门十大算法。
    7 I# t8 p1 N' g# J, c( h% ]+ m/ o( R' @' ^

    & P! N6 G. ]5 W! o  [入门十大算法是 枚举、排序、模拟、二分、双指针、差分法、位运算、贪心、迭代、分治。
    4 k; M( j5 l+ p7 _+ Y% g对于这十大算法,我会逐步更新道这个专栏里面:《LeetCode算法全集》。
    5 H+ w4 `3 [: `5 H* Z1、枚举
    + j  @4 o+ y: o* e) j枚举可以简单理解成for循环,从一个数组中遍历查找一个值,就是枚举;从一个数组中找到一个最大值,就是枚举;求数组所有数的和,也是枚举。
    ! S; ?  W4 h0 `5 T  ?% M! p对于枚举而言,基本就是循环语句的语法学会,这个算法就算学会了。
    , Z/ }" r+ u* t5 e1 |. d6 w" H2 O2、排序- \, u& q4 [" V. q/ Y
    既然是入门,千万不要去看快排、希尔排序这种冷门排序。
    , Z9 M! [' r. _5 ?0 B" s% p# r冒泡排序、选择排序、简单插入排序 原理好懂,先看懂再说,其他不管。因为这三者都是基于枚举的。; ~3 c) A# m8 S+ @% j- }5 ]
    C中有现成qsort排序函数,C++中有现成 sort排序函数,直接拿来用,等算法进阶时再回头来看快速排序的算法实现。
    7 U- I. {) ~& J  l3、模拟6 H) B8 ?9 S$ d( ~- S* K, n
    模拟就是要求做什么,你就做什么,完全不要去考虑效率问题。
    & w, R3 o& U4 t" W( z; n7 W不管时间复杂度 和 空间复杂度,放手去做!* |% P6 ?/ s3 c6 q4 a) X; S
    但是,有时候模拟题需要一些复杂的数据结构,所以模拟题难起来也可以很男,难上加难。% ^' E2 }8 m& a' X1 v( l
    4、二分2 z+ d* n- H6 {+ Z0 v2 o; z
    二分一般指二分查找,当然有时候也指代二分枚举。, _; |! u3 y2 X2 W" n2 _
    例如,在一个有序数组中查找值,我们一般这个干:
    ! F4 B! l' |$ L8 D1 d* z: D! N1)令初始情况下,数组下标从 0 开始,且数组长度为 n nn,则定义一个区间,它的左端点是 l = 0 l=0l=0,右端点是 r = n − 1 r = n-1r=n−1;
    ! m2 h  a4 W1 n8 F8 F# w* z  G2)生成一个区间中点 m i d = ( l + r ) / 2 mid = (l + r) / 2mid=(l+r)/2,并且判断 m i d midmid 对应的数组元素和给定的目标值的大小关系,主要有三种:
    ; }- `9 M0 {. P  2.a)目标值 等于 数组元素,直接返回 m i d midmid;
    + T( @' O% m: N. J* u  2.b)目标值 大于 数组元素,则代表目标值应该出现在区间 [ m i d + 1 , r ] [mid+1, r][mid+1,r],迭代左区间端点:l = m i d + 1 l = mid + 1l=mid+1;
    : \/ |3 f: d' }7 X. i, |2 b, U  2.c)目标值 小于 数组元素,则代表目标值应该出现在区间 [ l , m i d − 1 ] [l, mid-1][l,mid−1],迭代右区间端点:r = m i d − 1 r = mid - 1r=mid−1;
    % @( v2 E- E( q+ W3)如果这时候 l > r l > rl>r,则说明没有找到目标值,返回 − 1 -1−1;否则,回到 2)继续迭代。
    8 K  i. A/ X; m7 s' r- C8 W5、双指针  F1 y; q4 ~/ W% Z1 H1 p5 e
    双指针,主要是利用两个下标在一个数组上,根据问题的单调性,进行指针偏移,由于每个指针只往后偏移,所以时间复杂度可以达到 O ( n ) O(n)O(n),由于思想非常简单,所以出题时,热度不低。) r  w/ ~. P3 ^  b0 I% _

    & z3 v0 E% h9 \$ I: x( J
    * U) B9 F2 R0 Q# H
    6、差分法
    / q8 g' P* |0 F) i差分法一般配合前缀和。' E. z8 J0 B; w; ^/ c
    对于区间 [ l , r ] [l, r][l,r] 内求满足数量的数,可以利用差分法分解问题;6 i% H9 [4 C7 G
    假设 [ 0 , x ] [0, x][0,x] 内的 g o o d   n u m b e r good \ numbergood number 数量为 g x g_xg 7 F/ ]3 u" d9 `. k, {) `8 e
    x
    ) D& Z+ ?% v/ _8 D; e# k$ ~6 u​       
    ( y, c+ B3 r0 y/ z4 f3 F* P6 q2 _ ,那么区间 [ l , r ] [l, r][l,r] 内的数量就是 g r − g l − 1 g_r - g_{l-1}g * \( _( q8 n% s# E9 w# r
    r
    , a7 ^) Q9 I) i4 \​        " u( q0 T4 e/ o; b+ h6 D: a
    −g
    * Y" F/ c( l, P& ]l−1
    5 X& h; c3 ^2 ~0 L& F) T​        7 m  p) M' W! C1 R
    ;分别用同样的方法求出 g r g_rg
    $ V5 \2 M, o4 \, ~0 z; [0 ]r* K6 L5 B1 p, b. K6 B
    ​        & ]/ G0 [/ d$ E9 M; k  {
      和 g l − 1 g_{l-1}g 7 T$ w$ y6 ]0 x; J& m
    l−13 d( ^2 g0 G( @6 L$ P+ o
    ​        & V( O! m& y; {5 M* T
    ,再相减即可;7 T% G3 S. `  |. U; X2 \

    9 M1 N) ^& w' m# t8 [9 C
    ; @7 K% f( \1 T3 x8 A
    7、位运算: n7 k  j6 t' M
    位运算可以理解成对二进制数字上的每一个位进行操作的运算。
    ( f& s8 I& F0 g4 B6 H* n; A位运算分为 布尔位运算符 和 移位位运算符。
    + Y5 K7 j: A3 ]- L* m布尔位运算符又分为 位与(&)、位或(|)、异或(^)、按位取反(~);移位位运算符分为 左移(<<) 和 右移(>>)。
    1 D6 r3 }0 }* O( j* R如图所示:* r/ Z9 Z+ \- ^* {, r% ?% M

    & ^  n, l" [" T$ C& u

    ( x. G* O' y# K/ s6 r' m位运算的特点是语句短,但是可以干大事!+ ]. K! ]8 n! o- Q& L
    比如,请用一句话来判断一个数是否是2的幂,代码如下:- V0 l# }1 O+ X! P7 v3 `5 c- B
    !(x & (x - 1))1 E9 s6 O$ r  R7 Y2 e  P; p
    12 ?4 Z' I/ l/ G: o/ o7 z2 Z
    8、贪心
    6 p' {# [+ f3 s9 H贪心,一般就是按照当前最优解,去推算全局最优解。( y1 j- t, D/ M8 u7 Z
    所以,只有当当前最优解和全局最优解一致时才能用贪心算法。贪心算法的证明是比较难的,但是一些简单的贪心问题会比较直观,很容易看出来这个能够这么贪。
    7 W+ ]+ O5 g4 g9、迭代) t7 b% q1 ^0 B% q' @4 x
    每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值,周而复始,直到问题全部解决。
    9 x7 {: q' l* B. I# h* P10、分治
    5 E! P, u: i0 @分治,就是把问题分成若干子问题求解,子问题解决后,问题就解决了。一般利用递归实现。属于初学者比较头疼的内容。递归一开始学习的时候,一定要注意全局变量和局部变量的关系。/ I9 O1 v: Q, V! f! T+ B$ \
    5、算法进阶0 A/ R1 W2 N6 R- e2 i
    算法进阶这块是我打算规划自己未来十年去完成的一个项目,囊括了 大学生ACM程序设计竞赛、高中生的OI竞赛、LeetCode 职场面试算法 的算法全集,也就是之前网络上比较有名的 《夜深人静写算法》 系列,这可以说是我自己对自己的一个要求和目标吧。% E% Z. P7 l3 o( H# ~
    如果只是想进大厂,那么 算法入门 已经足够了,不需要再来看算法进阶了,当然如果对算法有浓厚兴趣,也欢迎和我一起打卡。由于内容较难,工作也比较忙,所以学的也比较慢,一周基本也只能更新一篇。2 J. d8 h1 v$ r2 ?
    这个系列主要分为以下几个大块内容:
    ( K$ _2 ?; p4 |  1)图论. Y. u7 M: O2 e# S
      2)动态规划
    * s8 T: R: ~0 q7 m4 G% N, R  3)计算几何
    : k1 N1 _! r# U( v. Y) y1 ]  4)数论
    1 O7 F+ }3 S: \  5)字符串匹配
    6 r( u% \7 T: P( B  6)高级数据结构(课本上学不到的)5 s# J. n5 s, P- k8 u
      7)杂项算法
    7 k1 o  j- X) k5 x/ m6 ~( \
      x: D* Z) w. U7 u; k2 X2 `" M. C
    " r" I+ k4 K" H1 F
    先来看下思维导图,然后我大致讲一下每一类算法各自的特点,以及学习方式:! G, w* U, F/ O$ D

    7 v6 i( u, Z0 F7 H$ n
    - I/ A1 a& G' }8 c0 r

    ' L$ [0 o) ^1 ^3 p% |! n
    % v/ F. n8 \, g8 Y8 o7 c; K
    1)图论
    4 C3 T" F, ~4 }, g1、搜索概览) {8 Q1 b) {7 Q1 Z4 L
    图论主要围绕搜索算法进行展开。搜索算法的原理就是枚举。利用计算机的高性能,给出人类制定好的规则,枚举出所有可行的情况,找到可行解或者最优解。. O. w& h% Z7 M0 P- g
    * N, g" Z- t+ P8 O

    2 \+ h4 F; r% C& e比较常见的搜索算法是 深度优先搜索(又叫深度优先遍历) 和 广度优先搜索(又叫广度优先遍历 或者 宽度优先遍历)。各种图论的算法基本都是依靠这两者进行展开的。
    $ k: P7 @- R% n1 o2、深度优先搜索/ y/ Y3 i) X+ \2 W& B2 j
    深度优先搜索一般用来求可行解,利用剪枝进行优化,在树形结构的图上用处较多;而广度优先搜索一般用来求最优解,配合哈希表进行状态空间的标记,从而避免重复状态的计算;
    4 {* g7 m; {* G原则上,天下万物皆可搜,只是时间已惘然。搜索会有大量的重复状态出现,这里的状态和动态规划的状态是同一个概念,所以有时候很难分清到底是用搜索还是动态规划。7 h' d; m; ~9 y  H; P& D8 S8 w. Q
    但是,大体上还是有迹可循的,如果这个状态不能映射到数组被缓存下来,那么大概率就是需要用搜索来求解的。
    4 l, e, T9 N% C6 L- B4 F+ v如图所示,代表的是一个深度优先搜索的例子,红色实箭头表示搜索路径,蓝色虚箭头表示回溯路径。
    4 ^: q% e  n  V& I( Q# {) m% S; s; T4 [6 T! w
    " i& y7 z2 L( ^
    红色块表示往下搜索,蓝色块表示往上回溯,遍历序列为:3 D8 }0 z& k6 y) |
            0 -> 1 -> 3 -> 4 -> 5 -> 2 -> 6
    , k6 i2 t1 W- I5 z+ G0 @) b: z1, P4 L& F( c# u+ L; v+ Z/ L; {
    同样,搜索的例子还有:: d1 {& ~, c3 p* ^( Y4 c( G, G8 R4 J- ]

      A; i; d/ j! ~4 w7 d& H

    3 J) P8 y8 m6 G$ c计算的是利用递归实现的 n nn 的阶乘。. ?( f6 `9 M2 B! Q% X
    3、记忆化搜索& |0 [2 b( p% z& t
    对于斐波那契函数的求解,如下所示:
      h8 U2 ~! e$ Kf ( n ) = { 1 ( n = 0 ) 1 ( n = 1 ) f ( n − 1 ) + f ( n − 2 ) ( n > 2 ) f(n) =
    + b. E0 m2 q9 v⎧⎩⎨11f(n−1)+f(n−2)(n=0)(n=1)(n>2)9 l% \3 _. l5 I8 }7 e4 R3 W# F
    {1(n=0)1(n=1)f(n−1)+f(n−2)(n>2)% Q3 q# V# Y! V" ?  z* D
    f(n)= 1 n" ?5 W) C7 T3 w# q
    ⎩
    * Z, z$ F, G$ C' E$ r) \5 b⎪3 x1 O. I# D3 u5 q
    ⎨
    $ h, h+ W3 p' u9 S. D$ G7 y/ G# O; j⎪. H/ i% K/ _5 X- Q7 d$ ^
    ⎧
    $ ^4 ?5 h3 A6 k% m8 _​        0 v' I) O- `8 T% `5 a& A
      + n; a! Z3 {/ W) Q6 O1 a
    19 r/ J, q3 a* ~( x
    1- E7 u: ~0 ]# O( |: p# e  u1 U
    f(n−1)+f(n−2)- ?4 n7 u% Q: J7 {, L
    ​        0 u  E. N# U2 d9 j, ?* W0 T! O
      
    ( F; R3 v6 g/ Z* E# z(n=0)1 U9 r; h' u6 U, j( `/ o
    (n=1)
    - X+ p* x/ u+ k(n>2)
    ; }: W& C: L; n, W2 ^9 n​        & T, R- a5 f3 P% b7 `2 g) S+ c6 o! ?

    / L+ C& n6 {8 k* l0 q对于 f ( 5 ) f(5)f(5) 的求解,程序调用如下:
    $ b( c" M8 p6 M  h+ U4 D8 S3 t1 G5 e% r6 g' r7 `

    . `" u9 s: x$ p& o这个过程用到了很多重复状态的搜索,我们需要将它优化,一般将一些状态缓存起来。
    3 d# R2 D9 B6 w# `我们通过一个动图来感受一下:
    ) R+ p6 d" \% f( `! F$ w7 W
    4 C  ^$ H. y8 O. I. f8 L3 D! D

    9 ~9 K8 ^% I4 _6 V( d; {当第二次需要计算 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表达式为真,直接返回,不再需要往下递归计算,这样就把原本的 “递归二叉树” 转换成了 “递归链”, 从而将原本指数级的算法变成了多项式级别。- o" h) T( z6 Y6 Y
    这就是记忆化搜索,像这种把状态缓存起来的方法,就是动态规划的思想了。
    " `7 c& [. c- A4、广度优先搜索
    ! F0 Z6 k0 _+ A" {1 f单向广搜就是最简化情况下的广度优先搜索(Breadth First Search),以下简称为广搜。游戏开发过程中用到的比较广泛的 A* 寻路,就是广搜的加强版。& `9 ?( \0 S3 D7 ~- P" u: t, N
    我们通过一个动图来对广搜有一个初步的印象。! W$ `1 _1 b7 ]. T

    : ]+ |$ e. U( _2 }5 q
    . h/ j* G" L: U
    7 h9 Z6 i7 |. c" F/ F* \1 J

    3 X1 G) M8 E, Z5 y: _" L从图中可以看出,广搜的本质还是暴力枚举。即对于每个当前位置,枚举四个相邻可以行走的方向进行不断尝试,直到找到目的地。有点像洪水爆发,从一个源头开始逐渐蔓延开来,直到所有可达的区域都被洪水灌溉,所以我们也把这种算法称为 FloodFill。
    & g6 D) `) F' T/ A) ~0 @1 N4 p那么,如何把它描述成程序的语言呢?这里需要用到一种数据结构 —— 队列。( V' t: G: I: g; V; T% i# C7 N
    这时候,算法和数据结构就完美结合了。% k) K  Y$ Q" P
    2)动态规划
    ! o, ~9 S4 T+ ]; E动态规划算法三要素:, w, s- @5 H+ _+ p+ J& w- u7 P
      ①所有不同的子问题组成的表;0 A: `( a: \( X+ C  j2 W
      ②解决问题的依赖关系可以看成是一个图;
    , y; t( S" P0 ?' o5 `/ Q  ③填充子问题的顺序(即对②的图进行拓扑排序,填充的过程称为状态转移);: t! Y6 Q; F( a; n- H

    ' z# U5 u' J2 Y! D1 }$ r

    3 u  T. l' ~! e  i5 \如果子问题的数目为 O ( n t ) O(n^t)O(n ' N9 o& X# t( k. p4 M: b
    t  \3 a2 P) s- X- o- j- S, O
    ),每个子问题需要用到 O ( n e ) O(n^e)O(n
    % f# R5 f* C* W6 L% m' we+ }1 r) f, f' [* V/ m+ U& d
    ) 个子问题的结果,那么我们称它为 tD/eD 的问题,于是可以总结出四类常用的动态规划方程:(下面会把opt作为取最优值的函数(一般取 m i n minmin 或 m a x maxmax ), w ( j , i ) w(j, i)w(j,i)为一个实函数,其它变量都可以在常数时间计算出来)。  g% X$ q% s" Z/ k/ D6 f
    1、1D/1D
    " L; H+ L! [0 @8 U* S$ Jd [ i ] = o p t ( d [ j ] + w ( j , i ) ∣ 0 < = i < j ) d = opt( d[j] + w(j, i) | 0 <= i < j )
    / F; |6 g; T) l9 ?d=opt(d[j]+w(j,i)∣0<=i<j)
    + b- u& D8 }. X3 ~状态转移如图四所示(黄色块代表d [ i ] dd,绿色块代表d [ j ] d[j]d[j]):, a. o% e! t& f: Y9 U: c
    " u) p% A. m  e$ B7 e1 L4 v

    ; ~6 i* a+ g4 s/ o+ I6 N7 B: z这类状态转移方程一般出现在线性模型中。3 F+ G8 B6 g* r% f- ?
    2、2D/0D
    7 u! M! Q- f# F" r& ^0 j# h% R% K# cd [ 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} )3 e' ?' H. A4 J2 D; K; Y
    d[j]=opt(d[i−1][j]+x ! G3 R0 R6 W/ e7 k/ ]
    i
    0 f, O4 r1 w4 ]​        : R8 l: ]* `' E4 i
    ,d[j−1]+y
    " ~, e: m9 Z2 p% Rj
    & q5 M1 |" P  A9 n/ s; L& j2 W​       
    ! b' j8 M6 B. o! h3 t: ~/ z ,d[i−1][j−1]+z 3 H1 t  t( W5 H4 i1 c8 \) @9 g
    ij
    2 \, r: W2 C! K3 q1 ?& T​       
    ( f% [& r: Q  N )
    " y3 T, H6 h4 [" f6 ^* p( @状态转移如图四所示:2 [' d7 }+ s* l6 d9 H  C1 b
    ' {3 ~0 i% ~8 ~8 ]" Q

    , N0 F0 D0 K, x+ V, J2 B比较经典的问题是最长公共子序列、最小编辑距离。/ S: `% o4 n& ?: ?6 D
    有关最长公共子序列的问题,可以参考以下文章:夜深人静写算法(二十一)- 最长公共子序列1 V4 V. y& f5 \' C1 x6 L- r
    有关最小编辑距离的问题,可以参考以下文章:夜深人静写算法(二十二)- 最小编辑距离
    9 y. H* o8 W* _3、2D/1D4 H8 C' R! Z. ~
    d [ 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] )6 _/ H! A2 D* p4 \
    d[j]=w(i,j)+opt(d[k−1]+d[k][j])" d1 }2 L: J% A& B# N' \  \7 ]0 ~
    区间模型常用方程,如图所示:7 M% A7 |& d! V9 U4 w3 N5 ^

    2 w/ v- W. b5 m/ E5 l; N8 S, v

    ' |5 ]3 Q3 ?+ ~- L" P另外一种常用的 2D/1D 的方程为:2 c: d1 U5 t  W
    d [ 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 )
    7 S( r/ {8 R' x- dd[j]=opt(d[i−1][k]+w(i,j,k)∣k<j)9 [0 |. w/ M9 F2 W: R5 d6 r. z
    区间模型的详细内容可以参考以下这篇文章:夜深人静写算法(二十七)- 区间DP% l4 |1 B1 g" ~: b6 p, _
    4、2D/2D
    % _7 d6 m2 l$ p9 V. v6 k2 Jd [ 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)
    : v+ b4 C- G) Ud[j]=opt(d[i
    / ]- n5 b$ Z+ D. ~5 o% y′
    3 F$ a. d# j/ g. |$ Q- K. o ][j
    / j( s5 A$ @" W+ m9 L′7 D! h3 y3 O+ N* {" Q0 Y
    ]+w(i 5 b. x0 m2 e2 X) U3 u1 ?" z
    ′$ P% i  `1 }- Y" \/ S
    ,j / Z  p4 ]- K/ k) O
    ′! Y% g+ o8 D  G
    ,i,j)∣0<=i . O: q; E; B3 U5 N: Q0 {0 o
    ′1 q; Q4 D. ]/ |' E5 y
    <i,0<=j 5 b, s8 k. w' O* n; L- K
    ′
    / |. z+ S( q/ p6 e5 y <j)
    3 x- u% d8 I- e# X如图所示:" d, G, }- P/ M! T8 x

    6 K' b% L9 l2 L* |( R7 P

    , i: I( y! x1 a; m常见于二维的迷宫问题,由于复杂度比较大,所以一般配合数据结构优化,如线段树、树状数组等。
    & W! E+ i. ]) n对于一个tD/eD 的动态规划问题,在不经过任何优化的情况下,可以粗略得到一个时间复杂度是O ( n t + e ) O(n^ {t+e})O(n 7 n% ?2 v; {' [' ~
    t+e# T( ?( E0 T. Y, c. o
    ),空间复杂度是O ( n t ) O(n^t)O(n
    & w: L% G' a; r  ?: It  K$ [. x5 L8 N6 p& I
    ) 的算法,大多数情况下空间复杂度是很容易优化的,难点在于时间复杂度,后续章节将详细讲解各种情况下的动态规划优化算法。) D( S" c: V, ^
    3)计算几何
    8 p: W$ B/ Y. U; S+ o$ K3 Z计算几何的问题是代码量最大的。它是计算机科学的一个分支,以往的解析几何,是用代数的方法,建立坐标系去解决问题,但是很多时候需要付出一些代价,比如精度误差,而计算几何更多的是从几何角度,用向量的方法来尽量减少精度误差,例如:将除法转化为乘法、避免三角函数等近似运算 等等。
    - \8 w: ?5 z0 U5 H; G+ _. V0 D如果一个比赛中,有一道计算几何的题,那么至少,它不会是一道水题。
    6 [) {' p0 e% @+ H* b1、double 代替 float
    6 I5 S2 R- \5 C9 C( `c++ 中 double 的精度高于 float,对精度要求较高的问题,务必采用 double;9 c+ _$ x/ O  l! M) z3 J. X) a
    2、浮点数判定
    ; M0 z( H0 [$ H4 a由于浮点数(小数)中是有无理数的,即无限不循环小数,也就是小数点后的位数是无限的,在计算机存储的时候不可能全部存下来,一定是近似的存储的,所以浮点数一定是存在精度误差的(实际上,就算是有理数,也是存在误差的,这和计算机存储机制有关,这里不再展开,有兴趣可以参见我博客的文章:C++ 浮点数精度判定);
    & }& H: X$ h( F+ f两个浮点数是否相等,可以采用两数相减的绝对值小于某个精度来实现:
    4 p4 c2 [# m5 b( Aconst double eps = 1e-8;
    ; Y% K; G& Q. ]. r7 Xbool EQ(double a, double b) {
    " U; k) F3 y" y    return fabs(a - b) < eps;* e) P6 P0 e9 D: s+ R, D
    }
    % n4 w9 l; y4 L/ A6 D1
    + i: {9 E. }0 }$ Z4 f9 ^  p" m7 c2
    ) E. x) ^+ k) @! R! n1 B8 Y33 a- q7 N  N- Z! d
    4% Q5 \: u7 D+ L$ _4 P5 S
    并且可以用一个三值函数来确定某个数是零、大于零还是小于零:
    - \, s2 o, @, b- d0 _" S$ ~int threeValue(double d) {
    % x* F" u& {6 C0 c) ^& T+ k& l1 u    if (fabs(d) < eps)
    : k  O1 M! B# Z8 x  N        return 0;
    . Y; q3 z6 z& ^4 O0 C    return d > 0 ? 1 : -1;2 @9 c. |; d# N4 ?2 Y' {
    }
    1 q* v. y( Z" z* V- S; d1. w8 L) n7 I2 e. O1 c: K! p( h
    2
    4 E8 ?* H, O: V  u* n3
    + v$ s0 t1 _. u$ |) ^& q0 ]4$ S  `! T  q6 F1 `1 }/ U
    50 n- L' ?- ?* Y: Y6 `
    3、负零判定/ F! M/ A/ s1 P; R! t
    因为精度误差的存在,所以在输出的时候一定要注意,避免输出 -0.00:5 @) p' ?! n# c
        double v = -0.0000000001;/ ?$ K' E% i* @, X7 x! H
        printf("%.2lf\n", v);% G5 m: M0 r/ P( L8 d
    16 f' |6 e1 V% _5 I5 t5 U
    2/ m; G, X9 _- S: ^& S
    避免方法是先通过三值函数确定实际值是否为0,如果是0,则需要取完绝对值后再输出:- h# f7 [: {5 C; X
        double v = -0.0000000001;1 q% J5 @/ M- E4 h$ A2 F: z+ b
        if(threeValue(v) == 0) {% B' g8 p3 T3 v# |; B
            v = fabs(v);  M) D5 y- y, r9 {" h5 }4 p
        }
    4 ^! p1 S0 b; U& k3 n" ?% V    printf("%.2lf\n", v);
    9 T% Z( L  _4 N7 r1
    2 X" X+ D! Q! M8 s" j1 N2
    " x- y$ z5 W4 B3 u3- U$ z8 @& J/ C; Z' m2 ]
    4: q& \! M" r# }, F: H
    5
    , S1 w4 Q/ s( e% D0 `  R0 T4、避免三角函数、对数、开方、除法等; N" S% V4 ~3 u6 {, s0 a
    c++ 三角函数运算方法采用的是 CORDIC算法,一种利用迭代的方式进行求解的算法,其中还用到了开方运算,所以实际的算力消耗还是很大的,在实际求解问题的过程中,能够避免不用就尽量不用。
    $ `# x4 @3 i' g除法运算会带来精度误差,所以能够转换成乘法的也尽量转换为乘法运算。
    2 k) o  T- A' g7 x! Z5、系统性的学习
    " W( i4 n- ^) G7 U基础知识:点、向量、叉乘、点乘、旋转、线段、线段判交、三角形面积;. l2 q0 S# S. u9 n3 w' n
    进阶知识:多边形面积、凸多边形判定、点在多边形内判定;
    # P/ h4 J4 A! ?. B相关算法:二维凸包、三维凸包、旋转卡壳、多边形面积交、多边形面积并、多边形面积异或、多边形和圆的面积交、半平面交、最小覆盖圆、最小包围球、模拟退火。$ R5 x4 n: j6 o
    / v1 d( \+ {2 g5 A/ Q9 [

    , V7 r8 q0 T  t& G$ F0 D* m学习计算几何,最好是系统性的,刷题的过程中不断提炼出自己的模板。
    ) g2 N+ R3 l+ H8 e% D# Q0 A9 V! N4)数论
    7 j' J! J, K+ z, K0 Q6 q刷题的时候遇到不会的数论题,真的是很揪心,从头学起吧,内容实在是太多了,每个知识点都要证明吃透,不然下次遇到还是不会;不学吧,又不甘心,就是单纯的想把这个题过了,真是进退两难!; a9 K2 w, G$ u8 W* w
    数论对一个人的数学思维要求较高,但是一般也是一些固定的模式,所以把模板整理出来很重要。4 e, p7 K; Z$ N. o) B
    当然,数论也有简单问题,一般先做一些入门题提升信心。( {. Z7 Y, P; R4 L; z' K
    1、数论入门6 O. _9 m5 R4 s8 ^9 w( {
    主要是一些基本概念,诸如:3 n$ s: C- B) q$ d+ _7 M
    整除性、素数与合数、素数判定、素数筛选法、因数分解、算术基本定理、因子个数、因子和、最大公约数 (GCD) 和 最小公倍数 (LCM)、辗转相除、同余、模运算、快速幂取模、循环节;
    # U; x# n) v" ?2、数论四大定理
    ( [  W: p' Y: a4 F' K$ v3 ^这四个定理学完,可以KO很多题:! _- f, }9 W/ R& g( U& b
    欧拉定理、中国剩余定理、费马小定理、威尔逊定理! N: _/ }2 Z$ _3 o
    3、数论进阶
    # p* u: u* n6 l: |系统性的学习,基本也就这些内容了:& N" J& t. W- N1 K/ I( S
    扩展欧几里得、逆元、欧拉函数、同余方程组、扩展欧拉定理、RSA、卢卡斯定理、整数分块、狄利克雷卷积、莫比乌斯反演、大数判素、大数因子分解、大步小步离散对数等等。
    " ~. z4 }/ Z% _, ^5)字符串匹配
    4 _9 x7 O2 E5 [# h& d: h$ {字符串匹配学习路线比较明确。3 `( [' [8 [' g2 U# b6 i
    先学习前缀匹配:字典树。
    * y2 J4 z& I4 i, y- ?# K6 a然后可以简单看一下回文串判定算法:Manacher。
    % I! i2 ?1 o( E& F% D0 k3 v4 U  B以及经典的单字符串匹配算法:KMP。+ E' l: C+ S! O
    实际上平时最常用的还是 BM 算法,而ACM中基本不考察。* c5 {9 k- y! E0 T' K' I
    然后就是较为高阶的 前缀自动机、后缀数组、后缀树、后缀自动机了。
    3 W  c9 x* c0 F关于 算法学习路线 的内容到这里就结束了。
      z4 f, e- ?5 |  U3 l) x8 _) E如果还有不懂的问题,可以 想方设法 找到作者的微信进行在线咨询。+ r9 w/ e2 ^' D  `4 k) t. l6 R: B
    参考资料1 [  @5 T$ Q* Q5 X
    【阶段一】C语言学习资料:《光天化日学C语言》(日更)
    & U& c0 j" ?: b4 {. W# \: W【阶段二】C语言例题:《C语言入门100例》(日更)2 [/ M. o, Q! a) h( l2 b
    【阶段三】算法入门题集:《LeetCode算法全集》(日更)6 ]: g2 ~6 h6 b/ I& z- m& q
    【阶段四】算法进阶:《夜深人静写算法》(周更)
    ' s' b2 i$ c3 f* H% X————————————————
    4 L7 ^! `" W. W' Q5 @版权声明:本文为CSDN博主「英雄哪里出来」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。5 ~: O0 r: a& W' a, X1 u
    原文链接:https://blog.csdn.net/WhereIsHeroFrom/article/details/118382228% t( U- C5 O  G4 R

    ! m+ M& r0 g  k1 V( V* v0 `0 h6 I- _) q+ r- x3 {
    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:30 , Processed in 1.447260 second(s), 56 queries .

    回顶部